From dec47c7102cf0aa3a4debf002928db8e460c0d71 Mon Sep 17 00:00:00 2001 From: Ben Sturmfels Date: Fri, 5 Mar 2021 23:12:19 +1100 Subject: [PATCH] Apply `pyupgrade --py3-plus` to remove Python 2 compatibility code. --- mediagoblin/_compat.py | 7 +- mediagoblin/api/decorators.py | 2 +- mediagoblin/api/views.py | 54 ++-- mediagoblin/app.py | 6 +- mediagoblin/auth/tools.py | 18 +- mediagoblin/auth/views.py | 12 +- mediagoblin/db/base.py | 6 +- mediagoblin/db/migration_tools.py | 39 ++- mediagoblin/db/migrations.py | 43 ++- mediagoblin/db/migrations/env.py | 1 - ...a5e29_subtitle_plugin_initial_migration.py | 2 +- mediagoblin/db/mixin.py | 30 +- mediagoblin/db/models.py | 83 +++--- mediagoblin/db/open.py | 8 +- mediagoblin/db/util.py | 2 +- mediagoblin/decorators.py | 20 +- mediagoblin/edit/forms.py | 12 +- mediagoblin/edit/lib.py | 2 +- mediagoblin/edit/views.py | 51 ++-- mediagoblin/errormiddleware.py | 16 +- mediagoblin/gmg_commands/__init__.py | 2 +- mediagoblin/gmg_commands/addmedia.py | 5 +- mediagoblin/gmg_commands/alembic_commands.py | 2 +- mediagoblin/gmg_commands/assetlink.py | 4 +- mediagoblin/gmg_commands/batchaddmedia.py | 5 +- mediagoblin/gmg_commands/dbupdate.py | 26 +- mediagoblin/gmg_commands/deletemedia.py | 3 +- mediagoblin/gmg_commands/reprocess.py | 21 +- mediagoblin/gmg_commands/serve.py | 3 +- mediagoblin/gmg_commands/users.py | 35 ++- mediagoblin/gmg_commands/util.py | 4 +- mediagoblin/init/__init__.py | 6 +- mediagoblin/init/celery/__init__.py | 8 +- mediagoblin/init/celery/from_celery.py | 4 +- mediagoblin/init/config.py | 10 +- mediagoblin/listings/views.py | 8 +- mediagoblin/meddleware/__init__.py | 2 +- mediagoblin/meddleware/csrf.py | 2 +- mediagoblin/media_types/__init__.py | 18 +- mediagoblin/media_types/ascii/asciitoimage.py | 12 +- mediagoblin/media_types/ascii/processing.py | 8 +- .../media_types/audio/audiotospectrogram.py | 4 +- mediagoblin/media_types/audio/processing.py | 6 +- mediagoblin/media_types/audio/transcoders.py | 14 +- mediagoblin/media_types/blog/lib.py | 6 +- mediagoblin/media_types/blog/models.py | 4 +- mediagoblin/media_types/blog/views.py | 32 +-- mediagoblin/media_types/image/__init__.py | 2 +- mediagoblin/media_types/image/processing.py | 23 +- mediagoblin/media_types/pdf/processing.py | 10 +- .../media_types/raw_image/processing.py | 10 +- mediagoblin/media_types/stl/model_loader.py | 2 +- mediagoblin/media_types/stl/processing.py | 6 +- mediagoblin/media_types/tools.py | 4 +- mediagoblin/media_types/video/__init__.py | 2 +- mediagoblin/media_types/video/migrations.py | 12 +- mediagoblin/media_types/video/models.py | 2 +- mediagoblin/media_types/video/processing.py | 38 +-- mediagoblin/media_types/video/transcoders.py | 15 +- mediagoblin/media_types/video/util.py | 2 +- mediagoblin/mg_globals.py | 2 +- mediagoblin/moderation/forms.py | 24 +- mediagoblin/moderation/tools.py | 24 +- mediagoblin/moderation/views.py | 14 +- mediagoblin/notifications/__init__.py | 2 +- mediagoblin/notifications/task.py | 2 +- mediagoblin/oauth/oauth.py | 10 +- mediagoblin/oauth/views.py | 22 +- mediagoblin/plugins/api/__init__.py | 2 +- mediagoblin/plugins/api/tools.py | 6 +- mediagoblin/plugins/api/views.py | 14 +- mediagoblin/plugins/archivalook/models.py | 16 +- mediagoblin/plugins/archivalook/tools.py | 56 ++-- mediagoblin/plugins/archivalook/views.py | 24 +- mediagoblin/plugins/basic_auth/tools.py | 10 +- mediagoblin/plugins/basic_auth/views.py | 4 +- mediagoblin/plugins/flatpagesfile/__init__.py | 2 +- mediagoblin/plugins/httpapiauth/__init__.py | 2 +- mediagoblin/plugins/ldap/tools.py | 10 +- mediagoblin/plugins/ldap/views.py | 2 +- mediagoblin/plugins/metadata_display/lib.py | 4 +- mediagoblin/plugins/openid/models.py | 4 +- mediagoblin/plugins/openid/store.py | 4 +- mediagoblin/plugins/openid/views.py | 2 +- mediagoblin/plugins/persona/views.py | 2 +- mediagoblin/plugins/piwigo/tools.py | 12 +- mediagoblin/plugins/piwigo/views.py | 8 +- .../plugins/processing_info/__init__.py | 4 +- mediagoblin/plugins/raven/__init__.py | 4 +- mediagoblin/plugins/subtitles/views.py | 4 +- .../plugins/trim_whitespace/__init__.py | 5 +- mediagoblin/processing/__init__.py | 40 +-- mediagoblin/processing/task.py | 18 +- mediagoblin/storage/__init__.py | 11 +- mediagoblin/storage/cloudfiles.py | 9 +- mediagoblin/storage/filestorage.py | 2 +- mediagoblin/storage/mountstorage.py | 3 +- mediagoblin/submit/forms.py | 2 +- mediagoblin/submit/lib.py | 12 +- mediagoblin/submit/views.py | 16 +- mediagoblin/tests/test_api.py | 38 +-- mediagoblin/tests/test_auth.py | 86 +++--- mediagoblin/tests/test_basic_auth.py | 12 +- mediagoblin/tests/test_config.py | 3 +- mediagoblin/tests/test_edit.py | 50 ++-- mediagoblin/tests/test_exif.py | 120 ++++---- mediagoblin/tests/test_globals.py | 2 +- mediagoblin/tests/test_ldap.py | 36 +-- mediagoblin/tests/test_legacy_api.py | 12 +- mediagoblin/tests/test_metadata.py | 2 +- mediagoblin/tests/test_misc.py | 14 +- mediagoblin/tests/test_modelmethods.py | 99 ++++--- mediagoblin/tests/test_moderation.py | 90 +++--- mediagoblin/tests/test_notifications.py | 32 +-- mediagoblin/tests/test_oauth1.py | 4 +- mediagoblin/tests/test_openid.py | 62 ++-- mediagoblin/tests/test_persona.py | 36 +-- mediagoblin/tests/test_piwigo.py | 6 +- mediagoblin/tests/test_pluginapi.py | 10 +- mediagoblin/tests/test_privileges.py | 46 +-- mediagoblin/tests/test_processing.py | 6 +- mediagoblin/tests/test_reporting.py | 54 ++-- mediagoblin/tests/test_response.py | 5 +- mediagoblin/tests/test_sql_migrations.py | 266 +++++++++--------- mediagoblin/tests/test_storage.py | 21 +- mediagoblin/tests/test_submission.py | 134 ++++----- mediagoblin/tests/test_subtitles.py | 12 +- mediagoblin/tests/test_tags.py | 20 +- mediagoblin/tests/test_tools.py | 7 +- mediagoblin/tests/test_util.py | 38 +-- mediagoblin/tests/test_workbench.py | 2 +- mediagoblin/tests/tools.py | 18 +- mediagoblin/tools/common.py | 4 +- mediagoblin/tools/crypto.py | 2 +- mediagoblin/tools/exif.py | 14 +- mediagoblin/tools/files.py | 2 +- mediagoblin/tools/licenses.py | 2 +- mediagoblin/tools/mail.py | 9 +- mediagoblin/tools/metadata.py | 7 +- mediagoblin/tools/pagination.py | 6 +- mediagoblin/tools/pluginapi.py | 4 +- mediagoblin/tools/processing.py | 8 +- mediagoblin/tools/request.py | 6 +- mediagoblin/tools/response.py | 6 +- mediagoblin/tools/routing.py | 8 +- mediagoblin/tools/session.py | 2 +- mediagoblin/tools/staticdirect.py | 12 +- mediagoblin/tools/subtitles.py | 2 +- mediagoblin/tools/template.py | 10 +- mediagoblin/tools/text.py | 12 +- mediagoblin/tools/timesince.py | 1 - mediagoblin/tools/translate.py | 12 +- mediagoblin/tools/url.py | 4 +- mediagoblin/tools/workbench.py | 6 +- mediagoblin/user_pages/forms.py | 6 +- mediagoblin/user_pages/views.py | 40 +-- mediagoblin/views.py | 2 +- setup.py | 4 +- 158 files changed, 1353 insertions(+), 1391 deletions(-) diff --git a/mediagoblin/_compat.py b/mediagoblin/_compat.py index 9164d5fc..c992f44e 100644 --- a/mediagoblin/_compat.py +++ b/mediagoblin/_compat.py @@ -3,15 +3,12 @@ import warnings import six -if six.PY3: - from email.mime.text import MIMEText -else: - from email.MIMEText import MIMEText +from email.mime.text import MIMEText def encode_to_utf8(method): def wrapper(self): - if six.PY2 and isinstance(method(self), six.text_type): + if six.PY2 and isinstance(method(self), str): return method(self).encode('utf-8') return method(self) functools.update_wrapper(wrapper, method, ['__name__', '__doc__']) diff --git a/mediagoblin/api/decorators.py b/mediagoblin/api/decorators.py index b86099bd..e79d4abf 100644 --- a/mediagoblin/api/decorators.py +++ b/mediagoblin/api/decorators.py @@ -36,7 +36,7 @@ def user_has_privilege(privilege_name): @require_active_login def wrapper(request, *args, **kwargs): if not request.user.has_privilege(privilege_name): - error = "User '{0}' needs '{1}' privilege".format( + error = "User '{}' needs '{}' privilege".format( request.user.username, privilege_name ) diff --git a/mediagoblin/api/views.py b/mediagoblin/api/views.py index dfa9dfa2..b78a19e3 100644 --- a/mediagoblin/api/views.py +++ b/mediagoblin/api/views.py @@ -62,7 +62,7 @@ def profile_endpoint(request): if user is None: username = request.matchdict["username"] return json_error( - "No such 'user' with username '{0}'".format(username), + "No such 'user' with username '{}'".format(username), status=404 ) @@ -77,7 +77,7 @@ def user_endpoint(request): if user is None: username = request.matchdict["username"] return json_error( - "No such 'user' with username '{0}'".format(username), + "No such 'user' with username '{}'".format(username), status=404 ) @@ -90,14 +90,14 @@ def user_endpoint(request): @oauth_required @csrf_exempt -@user_has_privilege(u'uploader') +@user_has_privilege('uploader') def uploads_endpoint(request): """ Endpoint for file uploads """ username = request.matchdict["username"] requested_user = LocalUser.query.filter(LocalUser.username==username).first() if requested_user is None: - return json_error("No such 'user' with id '{0}'".format(username), 404) + return json_error("No such 'user' with id '{}'".format(username), 404) if request.method == "POST": # Ensure that the user is only able to upload to their own @@ -123,7 +123,7 @@ def uploads_endpoint(request): if not filenames: return json_error('Unknown mimetype: {}'.format(mimetype), status=415) - filename = 'unknown{0}'.format(filenames[0]) + filename = 'unknown{}'.format(filenames[0]) file_data = FileStorage( stream=io.BytesIO(request.data), @@ -153,13 +153,13 @@ def inbox_endpoint(request, inbox=None): user = LocalUser.query.filter(LocalUser.username==username).first() if user is None: - return json_error("No such 'user' with id '{0}'".format(username), 404) + return json_error("No such 'user' with id '{}'".format(username), 404) # Only the user who's authorized should be able to read their inbox if user.id != request.user.id: return json_error( - "Only '{0}' can read this inbox.".format(user.username), + "Only '{}' can read this inbox.".format(user.username), 403 ) @@ -190,7 +190,7 @@ def inbox_endpoint(request, inbox=None): # build the inbox feed feed = { - "displayName": "Activities for {0}".format(user.username), + "displayName": "Activities for {}".format(user.username), "author": user.serialize(request), "objectTypes": ["activity"], "url": request.base_url, @@ -237,7 +237,7 @@ def feed_endpoint(request, outbox=None): # check if the user exists if requested_user is None: - return json_error("No such 'user' with id '{0}'".format(username), 404) + return json_error("No such 'user' with id '{}'".format(username), 404) if request.data: data = json.loads(request.data.decode()) @@ -270,7 +270,7 @@ def feed_endpoint(request, outbox=None): if obj.get("objectType", None) == "comment": # post a comment - if not request.user.has_privilege(u'commenter'): + if not request.user.has_privilege('commenter'): return json_error( "Privilege 'commenter' required to comment.", status=403 @@ -313,7 +313,7 @@ def feed_endpoint(request, outbox=None): if media is None: return json_response( - "No such 'image' with id '{0}'".format(media_id), + "No such 'image' with id '{}'".format(media_id), status=404 ) @@ -326,7 +326,7 @@ def feed_endpoint(request, outbox=None): if not media.unserialize(data["object"]): return json_error( - "Invalid 'image' with id '{0}'".format(media_id) + "Invalid 'image' with id '{}'".format(media_id) ) @@ -346,7 +346,7 @@ def feed_endpoint(request, outbox=None): # Oh no! We don't know about this type of object (yet) object_type = obj.get("objectType", None) return json_error( - "Unknown object type '{0}'.".format(object_type) + "Unknown object type '{}'.".format(object_type) ) # Updating existing objects @@ -377,7 +377,7 @@ def feed_endpoint(request, outbox=None): # Now try and find object if obj["objectType"] == "comment": - if not request.user.has_privilege(u'commenter'): + if not request.user.has_privilege('commenter'): return json_error( "Privilege 'commenter' required to comment.", status=403 @@ -388,7 +388,7 @@ def feed_endpoint(request, outbox=None): ).first() if comment is None: return json_error( - "No such 'comment' with id '{0}'.".format(obj_id) + "No such 'comment' with id '{}'.".format(obj_id) ) # Check that the person trying to update the comment is @@ -401,7 +401,7 @@ def feed_endpoint(request, outbox=None): if not comment.unserialize(data["object"], request): return json_error( - "Invalid 'comment' with id '{0}'".format(obj["id"]) + "Invalid 'comment' with id '{}'".format(obj["id"]) ) comment.save() @@ -423,7 +423,7 @@ def feed_endpoint(request, outbox=None): ).first() if image is None: return json_error( - "No such 'image' with the id '{0}'.".format(obj["id"]) + "No such 'image' with the id '{}'.".format(obj["id"]) ) # Check that the person trying to update the comment is @@ -436,7 +436,7 @@ def feed_endpoint(request, outbox=None): if not image.unserialize(obj): return json_error( - "Invalid 'image' with id '{0}'".format(obj_id) + "Invalid 'image' with id '{}'".format(obj_id) ) image.generate_slug() image.save() @@ -504,7 +504,7 @@ def feed_endpoint(request, outbox=None): if comment is None: return json_error( - "No such 'comment' with id '{0}'.".format(obj_id) + "No such 'comment' with id '{}'.".format(obj_id) ) # Make a delete activity @@ -533,7 +533,7 @@ def feed_endpoint(request, outbox=None): if entry is None: return json_error( - "No such 'image' with id '{0}'.".format(obj_id) + "No such 'image' with id '{}'.".format(obj_id) ) # Make the delete activity @@ -555,7 +555,7 @@ def feed_endpoint(request, outbox=None): elif request.method != "GET": return json_error( - "Unsupported HTTP method {0}".format(request.method), + "Unsupported HTTP method {}".format(request.method), status=501 ) @@ -645,7 +645,7 @@ def object_endpoint(request): try: object_id = request.matchdict["id"] except ValueError: - error = "Invalid object ID '{0}' for '{1}'".format( + error = "Invalid object ID '{}' for '{}'".format( request.matchdict["id"], object_type ) @@ -654,7 +654,7 @@ def object_endpoint(request): if object_type not in ["image"]: # not sure why this is 404, maybe ask evan. Maybe 400? return json_error( - "Unknown type: {0}".format(object_type), + "Unknown type: {}".format(object_type), status=404 ) @@ -668,7 +668,7 @@ def object_endpoint(request): media = MediaEntry.query.filter_by(public_id=public_id).first() if media is None: return json_error( - "Can't find '{0}' with ID '{1}'".format(object_type, object_id), + "Can't find '{}' with ID '{}'".format(object_type, object_id), status=404 ) @@ -685,7 +685,7 @@ def object_comments(request): ) media = MediaEntry.query.filter_by(public_id=public_id).first() if media is None: - return json_error("Can't find '{0}' with ID '{1}'".format( + return json_error("Can't find '{}' with ID '{}'".format( request.matchdict["object_type"], request.matchdict["id"] ), 404) @@ -702,7 +702,7 @@ def object_comments(request): ) }) - comments["displayName"] = "Replies to {0}".format(comments["url"]) + comments["displayName"] = "Replies to {}".format(comments["url"]) comments["links"] = { "first": comments["url"], "self": comments["url"], @@ -805,7 +805,7 @@ def lrdd_lookup(request): if user is None: return json_error( - "Can't find 'user' with username '{0}'".format(username)) + "Can't find 'user' with username '{}'".format(username)) return json_response([ { diff --git a/mediagoblin/app.py b/mediagoblin/app.py index 1e0808bb..21c1841d 100644 --- a/mediagoblin/app.py +++ b/mediagoblin/app.py @@ -52,7 +52,7 @@ from mediagoblin.tools.transition import DISABLE_GLOBALS _log = logging.getLogger(__name__) -class Context(object): +class Context: """ MediaGoblin context object. @@ -65,7 +65,7 @@ class Context(object): pass -class MediaGoblinApp(object): +class MediaGoblinApp: """ WSGI application of MediaGoblin @@ -359,7 +359,7 @@ def paste_app_factory(global_config, **app_config): break if not mediagoblin_config: - raise IOError("Usable mediagoblin config not found.") + raise OSError("Usable mediagoblin config not found.") del app_config['config'] mgoblin_app = MediaGoblinApp(mediagoblin_config) diff --git a/mediagoblin/auth/tools.py b/mediagoblin/auth/tools.py index ae6fadf6..0312cc8f 100644 --- a/mediagoblin/auth/tools.py +++ b/mediagoblin/auth/tools.py @@ -47,12 +47,12 @@ def normalize_user_or_email_field(allow_email=True, allow_user=True, If is_login is True, does not check the length of username. """ - message = _(u'Invalid User name or email address.') - nomail_msg = _(u"This field does not take email addresses.") - nouser_msg = _(u"This field requires an email address.") + message = _('Invalid User name or email address.') + nomail_msg = _("This field does not take email addresses.") + nouser_msg = _("This field requires an email address.") def _normalize_field(form, field): - email = u'@' in field.data + email = '@' in field.data if email: # normalize email address casing if not allow_email: raise wtforms.ValidationError(nomail_msg) @@ -71,8 +71,8 @@ def normalize_user_or_email_field(allow_email=True, allow_user=True, EMAIL_VERIFICATION_TEMPLATE = ( - u"{uri}?" - u"token={verification_key}") + "{uri}?" + "token={verification_key}") def send_verification_email(user, request, email=None, @@ -121,11 +121,11 @@ def basic_extra_validation(register_form, *args): if users_with_username: register_form.username.errors.append( - _(u'Sorry, a user with that name already exists.')) + _('Sorry, a user with that name already exists.')) extra_validation_passes = False if users_with_email: register_form.email.errors.append( - _(u'Sorry, a user with that email address already exists.')) + _('Sorry, a user with that email address already exists.')) extra_validation_passes = False return extra_validation_passes @@ -144,7 +144,7 @@ def register_user(request, register_form): user.save() # log the user in - request.session['user_id'] = six.text_type(user.id) + request.session['user_id'] = str(user.id) request.session.save() # send verification email diff --git a/mediagoblin/auth/views.py b/mediagoblin/auth/views.py index 593d588d..08228d1b 100644 --- a/mediagoblin/auth/views.py +++ b/mediagoblin/auth/views.py @@ -45,7 +45,7 @@ def register(request): if 'pass_auth' not in request.template_env.globals: redirect_name = hook_handle('auth_no_pass_redirect') if redirect_name: - return redirect(request, 'mediagoblin.plugins.{0}.register'.format( + return redirect(request, 'mediagoblin.plugins.{}.register'.format( redirect_name)) else: return redirect(request, 'index') @@ -80,7 +80,7 @@ def login(request): if 'pass_auth' not in request.template_env.globals: redirect_name = hook_handle('auth_no_pass_redirect') if redirect_name: - return redirect(request, 'mediagoblin.plugins.{0}.login'.format( + return redirect(request, 'mediagoblin.plugins.{}.login'.format( redirect_name)) else: return redirect(request, 'index') @@ -100,7 +100,7 @@ def login(request): # set up login in session if login_form.stay_logged_in.data: request.session['stay_logged_in'] = True - request.session['user_id'] = six.text_type(user.id) + request.session['user_id'] = str(user.id) request.session.save() if request.form.get('next'): @@ -157,11 +157,11 @@ def verify_email(request): user = User.query.filter_by(id=int(token)).first() - if user and user.has_privilege(u'active') is False: + if user and user.has_privilege('active') is False: user.verification_key = None user.all_privileges.append( Privilege.query.filter( - Privilege.privilege_name==u'active').first()) + Privilege.privilege_name=='active').first()) user.save() @@ -196,7 +196,7 @@ def resend_activation(request): return redirect(request, 'mediagoblin.auth.login') - if request.user.has_privilege(u'active'): + if request.user.has_privilege('active'): messages.add_message( request, messages.ERROR, diff --git a/mediagoblin/db/base.py b/mediagoblin/db/base.py index c59b0ebf..d2595ce2 100644 --- a/mediagoblin/db/base.py +++ b/mediagoblin/db/base.py @@ -25,7 +25,7 @@ if not DISABLE_GLOBALS: from sqlalchemy.orm import scoped_session, sessionmaker Session = scoped_session(sessionmaker()) -class FakeCursor(object): +class FakeCursor: def __init__ (self, cursor, mapper, filter=None): self.cursor = cursor @@ -50,7 +50,7 @@ class FakeCursor(object): r = self.cursor.slice(*args, **kwargs) return list(six.moves.filter(self.filter, six.moves.map(self.mapper, r))) -class GMGTableBase(object): +class GMGTableBase: # Deletion types HARD_DELETE = "hard-deletion" SOFT_DELETE = "soft-deletion" @@ -194,7 +194,7 @@ class GMGTableBase(object): Base = declarative_base(cls=GMGTableBase) -class DictReadAttrProxy(object): +class DictReadAttrProxy: """ Maps read accesses to obj['key'] to obj.key and hides all the rest of the obj diff --git a/mediagoblin/db/migration_tools.py b/mediagoblin/db/migration_tools.py index 852f35ee..149aab4d 100644 --- a/mediagoblin/db/migration_tools.py +++ b/mediagoblin/db/migration_tools.py @@ -14,7 +14,6 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from __future__ import unicode_literals import logging import os @@ -36,7 +35,7 @@ class TableAlreadyExists(Exception): pass -class MigrationManager(object): +class MigrationManager: """ Migration handling tool. @@ -148,7 +147,7 @@ class MigrationManager(object): # Maybe in the future just print out a "Yikes!" or something? if model.__table__.exists(self.session.bind): raise TableAlreadyExists( - u"Intended to create table '%s' but it already exists" % + "Intended to create table '%s' but it already exists" % model.__table__.name) self.migration_model.metadata.create_all( @@ -171,26 +170,26 @@ class MigrationManager(object): """ if self.database_current_migration is None: self.printer( - u'~> Woulda initialized: %s\n' % self.name_for_printing()) - return u'inited' + '~> Woulda initialized: %s\n' % self.name_for_printing()) + return 'inited' migrations_to_run = self.migrations_to_run() if migrations_to_run: self.printer( - u'~> Woulda updated %s:\n' % self.name_for_printing()) + '~> Woulda updated %s:\n' % self.name_for_printing()) for migration_number, migration_func in migrations_to_run(): self.printer( - u' + Would update %s, "%s"\n' % ( + ' + Would update {}, "{}"\n'.format( migration_number, migration_func.func_name)) - return u'migrated' + return 'migrated' def name_for_printing(self): - if self.name == u'__main__': - return u"main mediagoblin tables" + if self.name == '__main__': + return "main mediagoblin tables" else: - return u'plugin "%s"' % self.name + return 'plugin "%s"' % self.name def init_or_migrate(self): """ @@ -213,36 +212,36 @@ class MigrationManager(object): # - print / inform the user # - return 'inited' if migration_number is None: - self.printer(u"-> Initializing %s... " % self.name_for_printing()) + self.printer("-> Initializing %s... " % self.name_for_printing()) self.init_tables() # auto-set at latest migration number self.create_new_migration_record() - self.printer(u"done.\n") + self.printer("done.\n") self.set_current_migration() - return u'inited' + return 'inited' # Run migrations, if appropriate. migrations_to_run = self.migrations_to_run() if migrations_to_run: self.printer( - u'-> Updating %s:\n' % self.name_for_printing()) + '-> Updating %s:\n' % self.name_for_printing()) for migration_number, migration_func in migrations_to_run: self.printer( - u' + Running migration %s, "%s"... ' % ( + ' + Running migration {}, "{}"... '.format( migration_number, migration_func.__name__)) migration_func(self.session) self.set_current_migration(migration_number) self.printer('done.\n') - return u'migrated' + return 'migrated' # Otherwise return None. Well it would do this anyway, but # for clarity... ;) return None -class RegisterMigration(object): +class RegisterMigration: """ Tool for registering migrations @@ -348,9 +347,9 @@ def populate_table_foundations(session, foundations, name, Create the table foundations (default rows) as layed out in FOUNDATIONS in mediagoblin.db.models """ - printer(u'Laying foundations for %s:\n' % name) + printer('Laying foundations for %s:\n' % name) for Model, rows in foundations.items(): - printer(u' + Laying foundations for %s table\n' % + printer(' + Laying foundations for %s table\n' % (Model.__name__)) for parameters in rows: new_row = Model(**parameters) diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index 55d64294..e6a80310 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -14,7 +14,6 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from __future__ import print_function import datetime import uuid @@ -267,11 +266,11 @@ def mediaentry_new_slug_era(db): for row in db.execute(media_table.select()): # no slug, try setting to an id if not row.slug: - append_garbage_till_unique(row, six.text_type(row.id)) + append_garbage_till_unique(row, str(row.id)) # has "=" or ":" in it... we're getting rid of those - elif u"=" in row.slug or u":" in row.slug: + elif "=" in row.slug or ":" in row.slug: append_garbage_till_unique( - row, row.slug.replace(u"=", u"-").replace(u":", u"-")) + row, row.slug.replace("=", "-").replace(":", "-")) db.commit() @@ -296,7 +295,7 @@ def unique_collections_slug(db): existing_slugs[row.creator].append(row.slug) for row_id in slugs_to_change: - new_slug = six.text_type(uuid.uuid4()) + new_slug = str(uuid.uuid4()) db.execute(collection_table.update(). where(collection_table.c.id == row_id). values(slug=new_slug)) @@ -428,9 +427,9 @@ class Client_v0(declarative_base()): def __repr__(self): if self.application_name: - return "".format(self.application_name, self.id) + return "".format(self.application_name, self.id) else: - return "".format(self.id) + return "".format(self.id) class RequestToken_v0(declarative_base()): """ @@ -445,7 +444,7 @@ class RequestToken_v0(declarative_base()): used = Column(Boolean, default=False) authenticated = Column(Boolean, default=False) verifier = Column(Unicode, nullable=True) - callback = Column(Unicode, nullable=False, default=u"oob") + callback = Column(Unicode, nullable=False, default="oob") created = Column(DateTime, nullable=False, default=datetime.datetime.now) updated = Column(DateTime, nullable=False, default=datetime.datetime.now) @@ -589,12 +588,12 @@ class PrivilegeUserAssociation_v0(declarative_base()): primary_key=True) -PRIVILEGE_FOUNDATIONS_v0 = [{'privilege_name':u'admin'}, - {'privilege_name':u'moderator'}, - {'privilege_name':u'uploader'}, - {'privilege_name':u'reporter'}, - {'privilege_name':u'commenter'}, - {'privilege_name':u'active'}] +PRIVILEGE_FOUNDATIONS_v0 = [{'privilege_name':'admin'}, + {'privilege_name':'moderator'}, + {'privilege_name':'uploader'}, + {'privilege_name':'reporter'}, + {'privilege_name':'commenter'}, + {'privilege_name':'active'}] # vR1 stands for "version Rename 1". This only exists because we need # to deal with dropping some booleans and it's otherwise impossible @@ -656,11 +655,11 @@ def create_moderation_tables(db): db.execute( user_table.select().where( user_table.c.is_admin==False).where( - user_table.c.status==u"active")).fetchall(), + user_table.c.status=="active")).fetchall(), db.execute( user_table.select().where( user_table.c.is_admin==False).where( - user_table.c.status!=u"active")).fetchall()) + user_table.c.status!="active")).fetchall()) # Get the ids for each of the privileges so we can reference them ~~~~~~~~~ (admin_privilege_id, uploader_privilege_id, @@ -669,7 +668,7 @@ def create_moderation_tables(db): db.execute(privileges_table.select().where( privileges_table.c.privilege_name==privilege_name)).first()['id'] for privilege_name in - [u"admin",u"uploader",u"reporter",u"commenter",u"active"] + ["admin","uploader","reporter","commenter","active"] ] # Give each user the appopriate privileges depending whether they are an @@ -854,14 +853,14 @@ def revert_username_index(db): """ metadata = MetaData(bind=db.bind) user_table = inspect_table(metadata, "core__users") - indexes = dict( - [(index.name, index) for index in user_table.indexes]) + indexes = { + index.name: index for index in user_table.indexes} # index from unnecessary migration - users_uploader_index = indexes.get(u'ix_core__users_uploader') + users_uploader_index = indexes.get('ix_core__users_uploader') # index created from models.py after (unique=True, index=True) # was set in models.py - users_username_index = indexes.get(u'ix_core__users_username') + users_username_index = indexes.get('ix_core__users_username') if users_uploader_index is None and users_username_index is None: # We don't need to do anything. @@ -988,7 +987,7 @@ def activity_migration(db): # Get the ID of that generator gmg_generator = db.execute(generator_table.select( - generator_table.c.name==u"GNU Mediagoblin")).first() + generator_table.c.name=="GNU Mediagoblin")).first() # Now we want to modify the tables which MAY have an activity at some point diff --git a/mediagoblin/db/migrations/env.py b/mediagoblin/db/migrations/env.py index a6d05cd1..48b32ad6 100644 --- a/mediagoblin/db/migrations/env.py +++ b/mediagoblin/db/migrations/env.py @@ -1,4 +1,3 @@ -from __future__ import with_statement from alembic import context from sqlalchemy import engine_from_config, pool from logging.config import fileConfig diff --git a/mediagoblin/db/migrations/versions/afd3d1da5e29_subtitle_plugin_initial_migration.py b/mediagoblin/db/migrations/versions/afd3d1da5e29_subtitle_plugin_initial_migration.py index 565d4864..7177480a 100644 --- a/mediagoblin/db/migrations/versions/afd3d1da5e29_subtitle_plugin_initial_migration.py +++ b/mediagoblin/db/migrations/versions/afd3d1da5e29_subtitle_plugin_initial_migration.py @@ -24,7 +24,7 @@ def upgrade(): sa.Column('name', sa.Unicode(), nullable=False), sa.Column('filepath', PathTupleWithSlashes(), nullable=True), sa.Column('created', sa.DateTime(), nullable=False), - sa.ForeignKeyConstraint(['media_entry'], [u'core__media_entries.id'], ), + sa.ForeignKeyConstraint(['media_entry'], ['core__media_entries.id'], ), sa.PrimaryKeyConstraint('id') ) ### end Alembic commands ### diff --git a/mediagoblin/db/mixin.py b/mediagoblin/db/mixin.py index a6965bf4..1a47c46f 100644 --- a/mediagoblin/db/mixin.py +++ b/mediagoblin/db/mixin.py @@ -41,7 +41,7 @@ from mediagoblin.tools.text import cleaned_markdown_conversion from mediagoblin.tools.url import slugify from mediagoblin.tools.translate import pass_to_ugettext as _ -class CommentingMixin(object): +class CommentingMixin: """ Mixin that gives classes methods to get and add the comments on/to it @@ -80,9 +80,9 @@ class CommentingMixin(object): link = self.get_comment_link() if link is not None: link.delete() - super(CommentingMixin, self).soft_delete(*args, **kwargs) + super().soft_delete(*args, **kwargs) -class GeneratePublicIDMixin(object): +class GeneratePublicIDMixin: """ Mixin that ensures that a the public_id field is populated. @@ -118,7 +118,7 @@ class GeneratePublicIDMixin(object): self.save() return self.public_id -class UserMixin(object): +class UserMixin: object_type = "person" @property @@ -132,7 +132,7 @@ class UserMixin(object): user=self.username, **kwargs) -class GenerateSlugMixin(object): +class GenerateSlugMixin: """ Mixin to add a generate_slug method to objects. @@ -179,7 +179,7 @@ class GenerateSlugMixin(object): return # We don't want any empty string slugs - if slug == u"": + if slug == "": return # Otherwise, let's see if this is unique. @@ -188,7 +188,7 @@ class GenerateSlugMixin(object): # Can we just append the object's id to the end? if self.id: - slug_with_id = u"%s-%s" % (slug, self.id) + slug_with_id = "{}-{}".format(slug, self.id) if not self.check_slug_used(slug_with_id): self.slug = slug_with_id return # success! @@ -284,7 +284,7 @@ class MediaEntryMixin(GenerateSlugMixin, GeneratePublicIDMixin): if self.slug: return self.slug else: - return u'id:%s' % self.id + return 'id:%s' % self.id def url_for_self(self, urlgen, **extra_args): """ @@ -306,26 +306,26 @@ class MediaEntryMixin(GenerateSlugMixin, GeneratePublicIDMixin): Will return either the real thumbnail or a default fallback icon.""" # TODO: implement generic fallback in case MEDIA_MANAGER does # not specify one? - if u'thumb' in self.media_files: + if 'thumb' in self.media_files: thumb_url = self._app.public_store.file_url( - self.media_files[u'thumb']) + self.media_files['thumb']) else: # No thumbnail in media available. Get the media's # MEDIA_MANAGER for the fallback icon and return static URL # Raises FileTypeNotSupported in case no such manager is enabled manager = self.media_manager - thumb_url = self._app.staticdirector(manager[u'default_thumb']) + thumb_url = self._app.staticdirector(manager['default_thumb']) return thumb_url @property def original_url(self): """ Returns the URL for the original image will return self.thumb_url if original url doesn't exist""" - if u"original" not in self.media_files: + if "original" not in self.media_files: return self.thumb_url return self._app.public_store.file_url( - self.media_files[u"original"] + self.media_files["original"] ) @property @@ -442,7 +442,7 @@ class TextCommentMixin(GeneratePublicIDMixin): return cleaned_markdown_conversion(self.content) def __unicode__(self): - return u'<{klass} #{id} {actor} "{comment}">'.format( + return '<{klass} #{id} {actor} "{comment}">'.format( klass=self.__class__.__name__, id=self.id, actor=self.get_actor, @@ -514,7 +514,7 @@ class CollectionMixin(GenerateSlugMixin, GeneratePublicIDMixin): item.save(commit=commit) return item -class CollectionItemMixin(object): +class CollectionItemMixin: @property def note_html(self): """ diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 0974676a..11d7b275 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -18,7 +18,6 @@ TODO: indexes on foreignkeys, where useful. """ -from __future__ import print_function import logging import datetime @@ -114,9 +113,9 @@ class GenericModelReference(Base): # to prevent circular imports do import here registry = dict(Base._decl_class_registry).values() - self._TYPE_MAP = dict( - ((m.__tablename__, m) for m in registry if hasattr(m, "__tablename__")) - ) + self._TYPE_MAP = { + m.__tablename__: m for m in registry if hasattr(m, "__tablename__") + } setattr(type(self), "_TYPE_MAP", self._TYPE_MAP) return self.__class__._TYPE_MAP[model_type] @@ -271,7 +270,7 @@ class User(Base, UserMixin): for activity in Activity.query.filter_by(actor=self.id): activity.delete(**kwargs) - super(User, self).soft_delete(*args, **kwargs) + super().soft_delete(*args, **kwargs) def delete(self, *args, **kwargs): @@ -291,8 +290,8 @@ class User(Base, UserMixin): # Delete user, pass through commit=False/True in kwargs username = self.username - super(User, self).delete(*args, **kwargs) - _log.info('Deleted user "{0}" account'.format(username)) + super().delete(*args, **kwargs) + _log.info('Deleted user "{}" account'.format(username)) def has_privilege(self, privilege, allow_admin=True): """ @@ -311,7 +310,7 @@ class User(Base, UserMixin): priv = Privilege.query.filter_by(privilege_name=privilege).one() if priv in self.all_privileges: return True - elif allow_admin and self.has_privilege(u'admin', allow_admin=False): + elif allow_admin and self.has_privilege('admin', allow_admin=False): return True return False @@ -383,15 +382,15 @@ class LocalUser(User): # plugin data would be in a separate model def __repr__(self): - return '<{0} #{1} {2} {3} "{4}">'.format( + return '<{} #{} {} {} "{}">'.format( self.__class__.__name__, self.id, - 'verified' if self.has_privilege(u'active') else 'non-verified', - 'admin' if self.has_privilege(u'admin') else 'user', + 'verified' if self.has_privilege('active') else 'non-verified', + 'admin' if self.has_privilege('admin') else 'user', self.username) def get_public_id(self, host): - return "acct:{0}@{1}".format(self.username, host) + return "acct:{}@{}".format(self.username, host) def serialize(self, request): user = { @@ -423,7 +422,7 @@ class LocalUser(User): }, } - user.update(super(LocalUser, self).serialize(request)) + user.update(super().serialize(request)) return user class RemoteUser(User): @@ -438,7 +437,7 @@ class RemoteUser(User): } def __repr__(self): - return "<{0} #{1} {2}>".format( + return "<{} #{} {}>".format( self.__class__.__name__, self.id, self.webfinger @@ -466,9 +465,9 @@ class Client(Base): def __repr__(self): if self.application_name: - return "".format(self.application_name, self.id) + return "".format(self.application_name, self.id) else: - return "".format(self.id) + return "".format(self.id) class RequestToken(Base): """ @@ -483,7 +482,7 @@ class RequestToken(Base): used = Column(Boolean, default=False) authenticated = Column(Boolean, default=False) verifier = Column(Unicode, nullable=True) - callback = Column(Unicode, nullable=False, default=u"oob") + callback = Column(Unicode, nullable=False, default="oob") created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) updated = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) @@ -529,7 +528,7 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin): slug = Column(Unicode) description = Column(UnicodeText) # ?? media_type = Column(Unicode, nullable=False) - state = Column(Unicode, default=u'unprocessed', nullable=False) + state = Column(Unicode, default='unprocessed', nullable=False) # or use sqlalchemy.types.Enum? license = Column(Unicode) file_size = Column(Integer, default=0) @@ -636,7 +635,7 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin): """get the next 'newer' entry by this user""" media = MediaEntry.query.filter( (MediaEntry.actor == self.actor) - & (MediaEntry.state == u'processed') + & (MediaEntry.state == 'processed') & (MediaEntry.id > self.id)).order_by(MediaEntry.id).first() if media is not None: @@ -646,7 +645,7 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin): """get the next 'older' entry by this user""" media = MediaEntry.query.filter( (MediaEntry.actor == self.actor) - & (MediaEntry.state == u'processed') + & (MediaEntry.state == 'processed') & (MediaEntry.id < self.id)).order_by(desc(MediaEntry.id)).first() if media is not None: @@ -658,7 +657,7 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin): return the value of the key. """ media_file = MediaFile.query.filter_by(media_entry=self.id, - name=six.text_type(file_key)).first() + name=str(file_key)).first() if media_file: if metadata_key: @@ -671,11 +670,11 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin): Update the file_metadata of a MediaFile. """ media_file = MediaFile.query.filter_by(media_entry=self.id, - name=six.text_type(file_key)).first() + name=str(file_key)).first() file_metadata = media_file.file_metadata or {} - for key, value in six.iteritems(kwargs): + for key, value in kwargs.items(): file_metadata[key] = value media_file.file_metadata = file_metadata @@ -700,7 +699,7 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin): media_data.get_media_entry = self else: # Update old media data - for field, value in six.iteritems(kwargs): + for field, value in kwargs.items(): setattr(media_data, field, value) @memoized_property @@ -708,11 +707,7 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin): return import_component(self.media_type + '.models:BACKREF_NAME') def __repr__(self): - if six.PY2: - # obj.__repr__() should return a str on Python 2 - safe_title = self.title.encode('utf-8', 'replace') - else: - safe_title = self.title + safe_title = self.title return '<{classname} {id}: {title}>'.format( classname=self.__class__.__name__, @@ -724,7 +719,7 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin): for comment in self.get_comments(): comment.delete(*args, **kwargs) - super(MediaEntry, self).soft_delete(*args, **kwargs) + super().soft_delete(*args, **kwargs) def delete(self, del_orphan_tags=True, **kwargs): """Delete MediaEntry and all related files/attachments/comments @@ -744,7 +739,7 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin): # Returns list of files we failed to delete _log.error('No such files from the user "{1}" to delete: ' '{0}'.format(str(error), self.get_actor)) - _log.info('Deleted Media entry id "{0}"'.format(self.id)) + _log.info('Deleted Media entry id "{}"'.format(self.id)) # Related MediaTag's are automatically cleaned, but we might # want to clean out unused Tag's too. if del_orphan_tags: @@ -753,7 +748,7 @@ class MediaEntry(Base, MediaEntryMixin, CommentingMixin): from mediagoblin.db.util import clean_orphan_tags clean_orphan_tags(commit=False) # pass through commit=False/True in kwargs - super(MediaEntry, self).delete(**kwargs) + super().delete(**kwargs) def serialize(self, request, show_comments=True): """ Unserialize MediaEntry to object """ @@ -864,7 +859,7 @@ class FileKeynames(Base): name = Column(Unicode, unique=True) def __repr__(self): - return "" % (self.id, self.name) + return "".format(self.id, self.name) @classmethod def find_or_new(cls, name): @@ -893,7 +888,7 @@ class MediaFile(Base): {}) def __repr__(self): - return "" % (self.name, self.file_path) + return "".format(self.name, self.file_path) name_helper = relationship(FileKeynames, lazy="joined", innerjoin=True) name = association_proxy('name_helper', 'name', @@ -941,7 +936,7 @@ class Tag(Base): slug = Column(Unicode, nullable=False, unique=True) def __repr__(self): - return "" % (self.id, self.slug) + return "".format(self.id, self.slug) @classmethod def find_or_new(cls, slug): @@ -1040,7 +1035,7 @@ class Comment(Base): # fetch it from self.comment() raise AttributeError try: - _log.debug('Old attr is being accessed: {0}'.format(attr)) + _log.debug('Old attr is being accessed: {}'.format(attr)) return getattr(self.comment(), attr) # noqa except Exception as e: _log.error(e) @@ -1347,7 +1342,7 @@ class Notification(Base): seen='unseen' if not self.seen else 'seen') def __unicode__(self): - return u'<{klass} #{id}: {user}: {subject} ({seen})>'.format( + return '<{klass} #{id}: {user}: {subject} ({seen})>'.format( id=self.id, klass=self.__class__.__name__, user=self.user, @@ -1603,7 +1598,7 @@ class Activity(Base, ActivityMixin): def save(self, set_updated=True, *args, **kwargs): if set_updated: self.updated = datetime.datetime.now() - super(Activity, self).save(*args, **kwargs) + super().save(*args, **kwargs) class Graveyard(Base): """ Where models come to die """ @@ -1663,12 +1658,12 @@ MODELS = [ FOUNDATIONS = {User:user_foundations} """ -privilege_foundations = [{'privilege_name':u'admin'}, - {'privilege_name':u'moderator'}, - {'privilege_name':u'uploader'}, - {'privilege_name':u'reporter'}, - {'privilege_name':u'commenter'}, - {'privilege_name':u'active'}] +privilege_foundations = [{'privilege_name':'admin'}, + {'privilege_name':'moderator'}, + {'privilege_name':'uploader'}, + {'privilege_name':'reporter'}, + {'privilege_name':'commenter'}, + {'privilege_name':'active'}] FOUNDATIONS = {Privilege:privilege_foundations} ###################################################### diff --git a/mediagoblin/db/open.py b/mediagoblin/db/open.py index 8f81c8d9..3252f175 100644 --- a/mediagoblin/db/open.py +++ b/mediagoblin/db/open.py @@ -34,14 +34,14 @@ def set_models_as_attributes(obj): TODO: This should eventually be deprecated. """ - for k, v in six.iteritems(Base._decl_class_registry): + for k, v in Base._decl_class_registry.items(): setattr(obj, k, v) if not DISABLE_GLOBALS: from mediagoblin.db.base import Session - class DatabaseMaster(object): + class DatabaseMaster: def __init__(self, engine): self.engine = engine @@ -71,7 +71,7 @@ if not DISABLE_GLOBALS: else: from sqlalchemy.orm import sessionmaker - class DatabaseManager(object): + class DatabaseManager: """ Manage database connections. @@ -136,7 +136,7 @@ def load_models(app_config): try: __import__(plugin + ".models") except ImportError as exc: - _log.debug("Could not load {0}.models: {1}".format( + _log.debug("Could not load {}.models: {}".format( plugin, exc)) diff --git a/mediagoblin/db/util.py b/mediagoblin/db/util.py index 57e6b942..d787d653 100644 --- a/mediagoblin/db/util.py +++ b/mediagoblin/db/util.py @@ -49,7 +49,7 @@ def media_entries_for_tag_slug(dummy_db, tag_slug): .join(MediaEntry.tags_helper) \ .join(MediaTag.tag_helper) \ .filter( - (MediaEntry.state == u'processed') + (MediaEntry.state == 'processed') & (Tag.slug == tag_slug)) diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py index 2b8343b8..d64992df 100644 --- a/mediagoblin/decorators.py +++ b/mediagoblin/decorators.py @@ -58,11 +58,11 @@ def require_active_login(controller): @user_not_banned def new_controller_func(request, *args, **kwargs): if request.user and \ - not request.user.has_privilege(u'active'): + not request.user.has_privilege('active'): return redirect( request, 'mediagoblin.user_pages.user_home', user=request.user.username) - elif not request.user or not request.user.has_privilege(u'active'): + elif not request.user or not request.user.has_privilege('active'): next_url = urljoin( request.urlgen('mediagoblin.auth.login', qualified=True), @@ -128,7 +128,7 @@ def user_may_delete_media(controller): @wraps(controller) def wrapper(request, *args, **kwargs): uploader_id = kwargs['media'].actor - if not (request.user.has_privilege(u'admin') or + if not (request.user.has_privilege('admin') or request.user.id == uploader_id): raise Forbidden() @@ -145,7 +145,7 @@ def user_may_alter_collection(controller): def wrapper(request, *args, **kwargs): creator_id = request.db.LocalUser.query.filter_by( username=request.matchdict['user']).first().id - if not (request.user.has_privilege(u'admin') or + if not (request.user.has_privilege('admin') or request.user.id == creator_id): raise Forbidden() @@ -188,11 +188,11 @@ def get_user_media_entry(controller): media_slug = request.matchdict['media'] # if it starts with id: it actually isn't a slug, it's an id. - if media_slug.startswith(u'id:'): + if media_slug.startswith('id:'): try: media = MediaEntry.query.filter_by( id=int(media_slug[3:]), - state=u'processed', + state='processed', actor=user.id).first() except ValueError: raise NotFound() @@ -200,7 +200,7 @@ def get_user_media_entry(controller): # no magical id: stuff? It's a slug! media = MediaEntry.query.filter_by( slug=media_slug, - state=u'processed', + state='processed', actor=user.id).first() if not media: @@ -374,8 +374,8 @@ def require_admin_or_moderator_login(controller): @wraps(controller) def new_controller_func(request, *args, **kwargs): if request.user and \ - not (request.user.has_privilege(u'admin') - or request.user.has_privilege(u'moderator')): + not (request.user.has_privilege('admin') + or request.user.has_privilege('moderator')): raise Forbidden() elif not request.user: @@ -419,7 +419,7 @@ def oauth_required(controller): return json_response({"error": error}, status=400) # Fill user if not already - token = authorization[u"oauth_token"] + token = authorization["oauth_token"] request.access_token = AccessToken.query.filter_by(token=token).first() if request.access_token is not None and request.user is None: user_id = request.access_token.actor diff --git a/mediagoblin/edit/forms.py b/mediagoblin/edit/forms.py index 83e83c3c..d6d6d3f5 100644 --- a/mediagoblin/edit/forms.py +++ b/mediagoblin/edit/forms.py @@ -29,11 +29,11 @@ class WebsiteField(wtforms.StringField): def process_formdata(self, valuelist): if valuelist: data = valuelist[0] - if not data.startswith((u'http://', u'https://')): - data = u'http://' + data + if not data.startswith(('http://', 'https://')): + data = 'http://' + data self.data = data else: - super(WebsiteField, self).process_formdata(valuelist) + super().process_formdata(valuelist) class EditForm(wtforms.Form): @@ -143,7 +143,7 @@ class ChangeEmailForm(wtforms.Form): "Enter your password to prove you own this account.")) -class MetaDataValidator(object): +class MetaDataValidator: """ Custom validator which runs form data in a MetaDataForm through a jsonschema validator and passes errors recieved in jsonschema to wtforms. @@ -171,8 +171,8 @@ class MetaDataValidator(object): class MetaDataForm(wtforms.Form): - identifier = wtforms.StringField(_(u'Identifier'),[MetaDataValidator()]) - value = wtforms.StringField(_(u'Value')) + identifier = wtforms.StringField(_('Identifier'),[MetaDataValidator()]) + value = wtforms.StringField(_('Value')) class EditMetaDataForm(wtforms.Form): diff --git a/mediagoblin/edit/lib.py b/mediagoblin/edit/lib.py index 3f52376a..8f630136 100644 --- a/mediagoblin/edit/lib.py +++ b/mediagoblin/edit/lib.py @@ -19,6 +19,6 @@ def may_edit_media(request, media): """Check, if the request's user may edit the media details""" if media.actor == request.user.id: return True - if request.user.has_privilege(u'admin'): + if request.user.has_privilege('admin'): return True return False diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py index 717241e8..202d42a2 100644 --- a/mediagoblin/edit/views.py +++ b/mediagoblin/edit/views.py @@ -1,4 +1,3 @@ - # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. # # This program is free software: you can redistribute it and/or modify @@ -56,7 +55,7 @@ import mimetypes @require_active_login def edit_media(request, media): # If media is not processed, return NotFound. - if not media.state == u'processed': + if not media.state == 'processed': return render_404(request) if not may_edit_media(request, media): @@ -81,20 +80,20 @@ def edit_media(request, media): if slug_used: form.slug.errors.append( - _(u'An entry with that slug already exists for this user.')) + _('An entry with that slug already exists for this user.')) else: media.title = form.title.data media.description = form.description.data media.tags = convert_to_tag_list_of_dicts( form.tags.data) - media.license = six.text_type(form.license.data) or None + media.license = str(form.license.data) or None media.slug = slug media.save() return redirect_obj(request, media) - if request.user.has_privilege(u'admin') \ + if request.user.has_privilege('admin') \ and media.actor != request.user.id \ and request.method != 'POST': messages.add_message( @@ -120,7 +119,7 @@ UNSAFE_MIMETYPES = [ @require_active_login def edit_attachments(request, media): # If media is not processed, return NotFound. - if not media.state == u'processed': + if not media.state == 'processed': return render_404(request) if mg_globals.app_config['allow_attachments']: @@ -143,7 +142,7 @@ def edit_attachments(request, media): if mimetypes.guess_type( request.files['attachment_file'].filename)[0] in \ UNSAFE_MIMETYPES: - public_filename = secure_filename('{0}.notsafe'.format( + public_filename = secure_filename('{}.notsafe'.format( request.files['attachment_file'].filename)) else: public_filename = secure_filename( @@ -151,7 +150,7 @@ def edit_attachments(request, media): attachment_public_filepath \ = mg_globals.public_store.get_unique_filepath( - ['media_entries', six.text_type(media.id), 'attachment', + ['media_entries', str(media.id), 'attachment', public_filename]) attachment_public_file = mg_globals.public_store.get_file( @@ -201,7 +200,7 @@ def legacy_edit_profile(request): def edit_profile(request, url_user=None): # admins may edit any user profile if request.user.username != url_user.username: - if not request.user.has_privilege(u'admin'): + if not request.user.has_privilege('admin'): raise Forbidden(_("You can only edit your own profile.")) # No need to warn again if admin just submitted an edited profile @@ -226,15 +225,15 @@ def edit_profile(request, url_user=None): location=location) if request.method == 'POST' and form.validate(): - user.url = six.text_type(form.url.data) - user.bio = six.text_type(form.bio.data) + user.url = str(form.url.data) + user.bio = str(form.bio.data) # Save location if form.location.data and user.location is None: - user.get_location = Location(name=six.text_type(form.location.data)) + user.get_location = Location(name=str(form.location.data)) elif form.location.data: location = user.get_location - location.name = six.text_type(form.location.data) + location.name = str(form.location.data) location.save() else: user.location = None @@ -256,8 +255,8 @@ def edit_profile(request, url_user=None): 'form': form}) EMAIL_VERIFICATION_TEMPLATE = ( - u'{uri}?' - u'token={verification_key}') + '{uri}?' + 'token={verification_key}') @require_active_login @@ -324,7 +323,7 @@ def delete_account(request): """Delete a user completely""" user = request.user if request.method == 'POST': - if request.form.get(u'confirmed'): + if request.form.get('confirmed'): # Form submitted and confirmed. Actually delete the user account # Log out user and delete cookies etc. # TODO: Should we be using MG.auth.views.py:logout for this? @@ -384,17 +383,17 @@ def edit_collection(request, collection): form.title.data) elif slug_used: form.slug.errors.append( - _(u'A collection with that slug already exists for this user.')) + _('A collection with that slug already exists for this user.')) else: - collection.title = six.text_type(form.title.data) - collection.description = six.text_type(form.description.data) - collection.slug = six.text_type(form.slug.data) + collection.title = str(form.title.data) + collection.description = str(form.description.data) + collection.slug = str(form.slug.data) collection.save() return redirect_obj(request, collection) - if request.user.has_privilege(u'admin') \ + if request.user.has_privilege('admin') \ and collection.actor != request.user.id \ and request.method != 'POST': messages.add_message( @@ -508,19 +507,19 @@ def change_email(request): {'form': form, 'user': user}) -@user_has_privilege(u'admin') +@user_has_privilege('admin') @require_active_login @get_media_entry_by_id def edit_metadata(request, media): # If media is not processed, return NotFound. - if not media.state == u'processed': + if not media.state == 'processed': return render_404(request) form = forms.EditMetaDataForm( request.method == 'POST' and request.form or None) if request.method == "POST" and form.validate(): - metadata_dict = dict([(row['identifier'],row['value']) - for row in form.media_metadata.data]) + metadata_dict = {row['identifier']:row['value'] + for row in form.media_metadata.data} json_ld_metadata = None json_ld_metadata = compact_and_validate(metadata_dict) media.media_metadata = json_ld_metadata @@ -528,7 +527,7 @@ def edit_metadata(request, media): return redirect_obj(request, media) if len(form.media_metadata) == 0: - for identifier, value in six.iteritems(media.media_metadata): + for identifier, value in media.media_metadata.items(): if identifier == "@context": continue form.media_metadata.append_entry({ 'identifier':identifier, diff --git a/mediagoblin/errormiddleware.py b/mediagoblin/errormiddleware.py index da729d14..6484881a 100644 --- a/mediagoblin/errormiddleware.py +++ b/mediagoblin/errormiddleware.py @@ -20,15 +20,15 @@ MGOBLIN_ERROR_MESSAGE = """\
 .-------------------------.
 |     __            _     |
-|    -, \_,------,_//     |
-|     <\  ,--   --.\      |
+|    -, \\_,------,_//     |
+|     <\\  ,--   --.\\      |
 |      / (x  ) ( X )      |
-|      '  '--, ,--'\      |
-|     / \ -v-v-u-v /      |
-|     .  '.__.--__'.\     |
-|    / ',___/ / \__/'     |
-|    | |   ,'\_'/, ||     |
-|    \_|   | | | | ||     |
+|      '  '--, ,--'\\      |
+|     / \\ -v-v-u-v /      |
+|     .  '.__.--__'.\\     |
+|    / ',___/ / \\__/'     |
+|    | |   ,'\\_'/, ||     |
+|    \\_|   | | | | ||     |
 |     W',_ ||| |||_''     |
 |      |  '------'|       |
 |      |__|     |_|_      |
diff --git a/mediagoblin/gmg_commands/__init__.py b/mediagoblin/gmg_commands/__init__.py
index 0034fd98..963c3952 100644
--- a/mediagoblin/gmg_commands/__init__.py
+++ b/mediagoblin/gmg_commands/__init__.py
@@ -101,7 +101,7 @@ def main_cli():
             "otherwise mediagoblin.ini"))
 
     subparsers = parser.add_subparsers(help='sub-command help')
-    for command_name, command_struct in six.iteritems(SUBCOMMAND_MAP):
+    for command_name, command_struct in SUBCOMMAND_MAP.items():
         if 'help' in command_struct:
             subparser = subparsers.add_parser(
                 command_name, help=command_struct['help'])
diff --git a/mediagoblin/gmg_commands/addmedia.py b/mediagoblin/gmg_commands/addmedia.py
index 026f3495..fbec7071 100644
--- a/mediagoblin/gmg_commands/addmedia.py
+++ b/mediagoblin/gmg_commands/addmedia.py
@@ -14,7 +14,6 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see .
 
-from __future__ import print_function
 
 import os
 
@@ -95,7 +94,7 @@ def addmedia(args):
         if some_string is None:
             return None
         if six.PY2:
-            return six.text_type(some_string, 'utf-8')
+            return str(some_string, 'utf-8')
         return some_string
 
     try:
@@ -107,7 +106,7 @@ def addmedia(args):
             description=maybe_unicodeify(args.description),
             collection_slug=args.collection_slug,
             license=maybe_unicodeify(args.license),
-            tags_string=maybe_unicodeify(args.tags) or u"")
+            tags_string=maybe_unicodeify(args.tags) or "")
     except FileUploadLimit:
         print("This file is larger than the upload limits for this site.")
     except UserUploadLimit:
diff --git a/mediagoblin/gmg_commands/alembic_commands.py b/mediagoblin/gmg_commands/alembic_commands.py
index 4e73ec13..f5f982d1 100644
--- a/mediagoblin/gmg_commands/alembic_commands.py
+++ b/mediagoblin/gmg_commands/alembic_commands.py
@@ -35,7 +35,7 @@ class FudgedCommandLine(config.CommandLine):
             plugins = global_config.get('plugins', {}).keys()
             for plugin in plugins:
                 try:
-                    import_component('{0}.models:MODELS'.format(plugin))
+                    import_component('{}.models:MODELS'.format(plugin))
                 except ImportError:
                     # It doesn't really matter if there's no models to import
                     # here.
diff --git a/mediagoblin/gmg_commands/assetlink.py b/mediagoblin/gmg_commands/assetlink.py
index 148ebe9e..2f54808e 100644
--- a/mediagoblin/gmg_commands/assetlink.py
+++ b/mediagoblin/gmg_commands/assetlink.py
@@ -85,7 +85,7 @@ def link_theme_assets(theme, link_dir, printer=simple_printer):
     os.symlink(
         theme['assets_dir'].rstrip(os.path.sep),
         link_dir)
-    printer("Linked the theme's asset directory:\n  %s\nto:\n  %s\n" % (
+    printer("Linked the theme's asset directory:\n  {}\nto:\n  {}\n".format(
         theme['assets_dir'], link_dir))
 
 
@@ -128,7 +128,7 @@ def link_plugin_assets(plugin_static, plugins_link_dir, printer=simple_printer):
     os.symlink(
         plugin_static.file_path.rstrip(os.path.sep),
         link_dir)
-    printer('Linked asset directory for plugin "%s":\n  %s\nto:\n  %s\n' % (
+    printer('Linked asset directory for plugin "{}":\n  {}\nto:\n  {}\n'.format(
         plugin_static.name,
         plugin_static.file_path.rstrip(os.path.sep),
         link_dir))
diff --git a/mediagoblin/gmg_commands/batchaddmedia.py b/mediagoblin/gmg_commands/batchaddmedia.py
index 88fa3e5a..86c9425f 100644
--- a/mediagoblin/gmg_commands/batchaddmedia.py
+++ b/mediagoblin/gmg_commands/batchaddmedia.py
@@ -14,7 +14,6 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see .
 
-from __future__ import print_function, unicode_literals
 
 import csv
 import os
@@ -84,7 +83,7 @@ def batchaddmedia(args):
     abs_metadata_filename = os.path.abspath(metadata_path)
     abs_metadata_dir = os.path.dirname(abs_metadata_filename)
 
-    all_metadata = open(abs_metadata_filename, 'r')
+    all_metadata = open(abs_metadata_filename)
     media_metadata = csv.DictReader(all_metadata)
     for index, file_metadata in enumerate(media_metadata):
         if six.PY2:
@@ -159,7 +158,7 @@ Metadata was not uploaded.""".format(
                 file_abs_path = os.path.abspath(file_path)
             try:
                 media_file = open(file_abs_path, 'rb')
-            except IOError:
+            except OSError:
                 print(_("""\
 FAIL: Local file {filename} could not be accessed.
 {filename} will not be uploaded.""".format(filename=filename)))
diff --git a/mediagoblin/gmg_commands/dbupdate.py b/mediagoblin/gmg_commands/dbupdate.py
index 2700ccbc..988c0e26 100644
--- a/mediagoblin/gmg_commands/dbupdate.py
+++ b/mediagoblin/gmg_commands/dbupdate.py
@@ -36,7 +36,7 @@ def dbupdate_parse_setup(subparser):
     pass
 
 
-class DatabaseData(object):
+class DatabaseData:
     def __init__(self, name, models, migrations):
         self.name = name
         self.models = models
@@ -64,34 +64,34 @@ def gather_database_data(plugins):
 
     managed_dbdata.append(
         DatabaseData(
-            u'__main__', MAIN_MODELS, MAIN_MIGRATIONS))
+            '__main__', MAIN_MODELS, MAIN_MIGRATIONS))
 
     for plugin in plugins:
         try:
-            models = import_component('{0}.models:MODELS'.format(plugin))
+            models = import_component('{}.models:MODELS'.format(plugin))
         except ImportError as exc:
-            _log.debug('No models found for {0}: {1}'.format(
+            _log.debug('No models found for {}: {}'.format(
                 plugin,
                 exc))
 
             models = []
         except AttributeError as exc:
-            _log.warning('Could not find MODELS in {0}.models, have you '
-                         'forgotten to add it? ({1})'.format(plugin, exc))
+            _log.warning('Could not find MODELS in {}.models, have you '
+                         'forgotten to add it? ({})'.format(plugin, exc))
             models = []
 
         try:
-            migrations = import_component('{0}.migrations:MIGRATIONS'.format(
+            migrations = import_component('{}.migrations:MIGRATIONS'.format(
                 plugin))
         except ImportError as exc:
-            _log.debug('No migrations found for {0}: {1}'.format(
+            _log.debug('No migrations found for {}: {}'.format(
                 plugin,
                 exc))
 
             migrations = {}
         except AttributeError as exc:
-            _log.debug('Could not find MIGRATIONS in {0}.migrations, have you'
-                       'forgotten to add it? ({1})'.format(plugin, exc))
+            _log.debug('Could not find MIGRATIONS in {}.migrations, have you'
+                       'forgotten to add it? ({})'.format(plugin, exc))
             migrations = {}
 
         if models:
@@ -106,7 +106,7 @@ def run_foundations(db, global_config):
     Gather foundations data and run it.
     """
     from mediagoblin.db.models import FOUNDATIONS as MAIN_FOUNDATIONS
-    all_foundations = [(u"__main__", MAIN_FOUNDATIONS)]
+    all_foundations = [("__main__", MAIN_FOUNDATIONS)]
 
     Session = sessionmaker(bind=db.engine)
     session = Session()
@@ -116,7 +116,7 @@ def run_foundations(db, global_config):
     for plugin in plugins:
         try:
             foundations = import_component(
-                '{0}.models:FOUNDATIONS'.format(plugin))
+                '{}.models:FOUNDATIONS'.format(plugin))
             all_foundations.append((plugin, foundations))
         except ImportError as exc:
             continue
@@ -215,7 +215,7 @@ def sqam_migrations_to_run(db, app_config, global_config):
     # was never installed with any migrations
     from mediagoblin.db.models import MigrationData
     if Session().query(MigrationData).filter_by(
-            name=u"__main__").first() is None:
+            name="__main__").first() is None:
         return False
 
     # Setup media managers for all dbdata, run init/migrate and print info
diff --git a/mediagoblin/gmg_commands/deletemedia.py b/mediagoblin/gmg_commands/deletemedia.py
index bf50abde..4919f86a 100644
--- a/mediagoblin/gmg_commands/deletemedia.py
+++ b/mediagoblin/gmg_commands/deletemedia.py
@@ -14,7 +14,6 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see .
 
-from __future__ import print_function
 import sys
 
 from mediagoblin.gmg_commands import util as commands_util
@@ -28,7 +27,7 @@ def parser_setup(subparser):
 def deletemedia(args):
     app = commands_util.setup_app(args)
 
-    media_ids = set([int(mid) for mid in args.media_ids.split(',') if mid.isdigit()])
+    media_ids = {int(mid) for mid in args.media_ids.split(',') if mid.isdigit()}
     if not media_ids:
         print('Can\'t find any valid media ID(s).')
         sys.exit(1)
diff --git a/mediagoblin/gmg_commands/reprocess.py b/mediagoblin/gmg_commands/reprocess.py
index 85cae6df..b2a69819 100644
--- a/mediagoblin/gmg_commands/reprocess.py
+++ b/mediagoblin/gmg_commands/reprocess.py
@@ -14,7 +14,6 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see .
 
-from __future__ import print_function
 
 import argparse
 import os
@@ -146,7 +145,7 @@ def available(args):
         manager = get_processing_manager_for_type(media_type)
     except ProcessingManagerDoesNotExist:
         entry = MediaEntry.query.filter_by(id=args.id_or_type).first()
-        print('No such processing manager for {0}'.format(entry.media_type))
+        print('No such processing manager for {}'.format(entry.media_type))
 
     if args.state:
         processors = manager.list_all_processors_by_state(args.state)
@@ -171,7 +170,7 @@ def available(args):
     else:
         for processor in processors:
             if processor.description:
-                print(" - %s: %s" % (processor.name, processor.description))
+                print(" - {}: {}".format(processor.name, processor.description))
             else:
                 print(" - %s" % processor.name)
 
@@ -188,11 +187,11 @@ def run(args, media_id=None):
             processor_class = manager.get_processor(
                 args.reprocess_command, media_entry)
         except ProcessorDoesNotExist:
-            print('No such processor "%s" for media with id "%s"' % (
+            print('No such processor "{}" for media with id "{}"'.format(
                 args.reprocess_command, media_entry.id))
             return
         except ProcessorNotEligible:
-            print('Processor "%s" exists but media "%s" is not eligible' % (
+            print('Processor "{}" exists but media "{}" is not eligible'.format(
                 args.reprocess_command, media_entry.id))
             return
 
@@ -206,7 +205,7 @@ def run(args, media_id=None):
 
     except ProcessingManagerDoesNotExist:
         entry = MediaEntry.query.filter_by(id=media_id).first()
-        print('No such processing manager for {0}'.format(entry.media_type))
+        print('No such processing manager for {}'.format(entry.media_type))
 
 
 def bulk_run(args):
@@ -236,11 +235,11 @@ def thumbs(args):
                 processor_class = manager.get_processor(
                     'resize', media_entry)
             except ProcessorDoesNotExist:
-                print('No such processor "%s" for media with id "%s"' % (
+                print('No such processor "{}" for media with id "{}"'.format(
                     'resize', media_entry.id))
                 return
             except ProcessorNotEligible:
-                print('Processor "%s" exists but media "%s" is not eligible' % (
+                print('Processor "{}" exists but media "{}" is not eligible'.format(
                     'resize', media_entry.id))
                 return
 
@@ -248,7 +247,7 @@ def thumbs(args):
 
             # prepare filetype and size to be passed into reprocess_parser
             if args.size:
-                extra_args = 'thumb --{0} {1} {2}'.format(
+                extra_args = 'thumb --{} {} {}'.format(
                     processor_class.thumb_size,
                     args.size[0],
                     args.size[1])
@@ -263,7 +262,7 @@ def thumbs(args):
                 reprocess_info=reprocess_request)
 
         except ProcessingManagerDoesNotExist:
-            print('No such processing manager for {0}'.format(entry.media_type))
+            print('No such processing manager for {}'.format(entry.media_type))
 
 
 def initial(args):
@@ -279,7 +278,7 @@ def initial(args):
                 media_entry,
                 reprocess_action='initial')
         except ProcessingManagerDoesNotExist:
-            print('No such processing manager for {0}'.format(entry.media_type))
+            print('No such processing manager for {}'.format(entry.media_type))
 
 
 def reprocess(args):
diff --git a/mediagoblin/gmg_commands/serve.py b/mediagoblin/gmg_commands/serve.py
index 6ded1cfc..0e25a112 100644
--- a/mediagoblin/gmg_commands/serve.py
+++ b/mediagoblin/gmg_commands/serve.py
@@ -14,12 +14,11 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see .
 
-from __future__ import print_function
 
 from paste.deploy import loadapp, loadserver
 
 
-class ServeCommand(object):
+class ServeCommand:
 
     def loadserver(self, server_spec, name, relative_to, **kwargs):
         return loadserver(server_spec, name=name, relative_to=relative_to,
diff --git a/mediagoblin/gmg_commands/users.py b/mediagoblin/gmg_commands/users.py
index d1a8b72d..b61a5571 100644
--- a/mediagoblin/gmg_commands/users.py
+++ b/mediagoblin/gmg_commands/users.py
@@ -14,7 +14,6 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see .
 
-from __future__ import print_function
 
 import sys
 
@@ -41,7 +40,7 @@ def adduser(args):
     #TODO: Lets trust admins this do not validate Emails :)
     commands_util.setup_app(args)
 
-    args.username = six.text_type(commands_util.prompt_if_not_set(args.username, "Username:"))
+    args.username = str(commands_util.prompt_if_not_set(args.username, "Username:"))
     args.password = commands_util.prompt_if_not_set(args.password, "Password:",True)
     args.email = commands_util.prompt_if_not_set(args.email, "Email:")
 
@@ -52,36 +51,36 @@ def adduser(args):
         ).count()
 
     if users_with_username:
-        print(u'Sorry, a user with that name already exists.')
+        print('Sorry, a user with that name already exists.')
         sys.exit(1)
 
     else:
         # Create the user
         entry = db.LocalUser()
-        entry.username = six.text_type(args.username.lower())
-        entry.email = six.text_type(args.email)
+        entry.username = str(args.username.lower())
+        entry.email = str(args.email)
         entry.pw_hash = auth.gen_password_hash(args.password)
         default_privileges = [
             db.Privilege.query.filter(
-                db.Privilege.privilege_name==u'commenter').one(),
+                db.Privilege.privilege_name=='commenter').one(),
             db.Privilege.query.filter(
-                db.Privilege.privilege_name==u'uploader').one(),
+                db.Privilege.privilege_name=='uploader').one(),
             db.Privilege.query.filter(
-                db.Privilege.privilege_name==u'reporter').one(),
+                db.Privilege.privilege_name=='reporter').one(),
             db.Privilege.query.filter(
-                db.Privilege.privilege_name==u'active').one()
+                db.Privilege.privilege_name=='active').one()
         ]
         entry.all_privileges = default_privileges
         entry.save()
 
-        print(u"User created (and email marked as verified).")
+        print("User created (and email marked as verified).")
 
 
 def makeadmin_parser_setup(subparser):
     subparser.add_argument(
         'username',
         help="Username to give admin level",
-        type=six.text_type)
+        type=str)
 
 
 def makeadmin(args):
@@ -95,12 +94,12 @@ def makeadmin(args):
     if user:
         user.all_privileges.append(
             db.Privilege.query.filter(
-                db.Privilege.privilege_name==u'admin').one()
+                db.Privilege.privilege_name=='admin').one()
         )
         user.save()
-        print(u'The user %s is now an admin.' % args.username)
+        print('The user %s is now an admin.' % args.username)
     else:
-        print(u'The user %s doesn\'t exist.' % args.username)
+        print('The user %s doesn\'t exist.' % args.username)
         sys.exit(1)
 
 
@@ -108,7 +107,7 @@ def changepw_parser_setup(subparser):
     subparser.add_argument(
         'username',
         help="Username used to login",
-        type=six.text_type)
+        type=str)
     subparser.add_argument(
         'password',
         help="Your NEW supersecret word to login")
@@ -125,9 +124,9 @@ def changepw(args):
     if user:
         user.pw_hash = auth.gen_password_hash(args.password)
         user.save()
-        print(u'Password successfully changed for user %s.' % args.username)
+        print('Password successfully changed for user %s.' % args.username)
     else:
-        print(u'The user %s doesn\'t exist.' % args.username)
+        print('The user %s doesn\'t exist.' % args.username)
         sys.exit(1)
 
 
@@ -135,7 +134,7 @@ def deleteuser_parser_setup(subparser):
     subparser.add_argument(
         'username',
         help="Username to delete",
-        type=six.text_type)
+        type=str)
 
 
 def deleteuser(args):
diff --git a/mediagoblin/gmg_commands/util.py b/mediagoblin/gmg_commands/util.py
index 4a4a9a74..f27a9535 100644
--- a/mediagoblin/gmg_commands/util.py
+++ b/mediagoblin/gmg_commands/util.py
@@ -35,8 +35,8 @@ def prompt_if_not_set(variable, text, password=False):
     """
     if variable is None:
         if not password:
-            variable = six.moves.input(text + u' ')
+            variable = six.moves.input(text + ' ')
         else:
-            variable=getpass.getpass(text + u' ')
+            variable=getpass.getpass(text + ' ')
 
     return variable
diff --git a/mediagoblin/init/__init__.py b/mediagoblin/init/__init__.py
index 05a26792..e314c20d 100644
--- a/mediagoblin/init/__init__.py
+++ b/mediagoblin/init/__init__.py
@@ -112,8 +112,8 @@ def get_jinja_loader(user_template_path=None, current_theme=None,
     # Add plugin template paths next--takes precedence over
     # core templates.
     if plugin_template_paths is not None:
-        path_list.extend((jinja2.FileSystemLoader(path)
-                          for path in plugin_template_paths))
+        path_list.extend(jinja2.FileSystemLoader(path)
+                          for path in plugin_template_paths)
 
     # Add core templates last.
     path_list.append(jinja2.PackageLoader('mediagoblin', 'templates'))
@@ -133,7 +133,7 @@ def get_staticdirector(app_config):
 
     # Let plugins load additional paths
     for plugin_static in hook_runall("static_setup"):
-        direct_domains[plugin_static.name] = "%s/%s" % (
+        direct_domains[plugin_static.name] = "{}/{}".format(
             app_config['plugin_web_path'].rstrip('/'),
             plugin_static.name)
 
diff --git a/mediagoblin/init/celery/__init__.py b/mediagoblin/init/celery/__init__.py
index 9fb783bd..26f7cb61 100644
--- a/mediagoblin/init/celery/__init__.py
+++ b/mediagoblin/init/celery/__init__.py
@@ -58,7 +58,7 @@ def get_celery_settings_dict(app_config, global_config,
     celery_settings = {}
 
     # Add all celery settings from config
-    for key, value in six.iteritems(celery_conf):
+    for key, value in celery_conf.items():
         celery_settings[key] = value
 
     # TODO: use default result stuff here if it exists
@@ -122,7 +122,7 @@ def setup_celery_from_config(app_config, global_config,
     __import__(settings_module)
     this_module = sys.modules[settings_module]
 
-    for key, value in six.iteritems(celery_settings):
+    for key, value in celery_settings.items():
         setattr(this_module, key, value)
 
     if set_environ:
@@ -132,8 +132,8 @@ def setup_celery_from_config(app_config, global_config,
     # initiated
     from celery import current_app
 
-    _log.info('Setting celery configuration from object "{0}"'.format(
+    _log.info('Setting celery configuration from object "{}"'.format(
         settings_module))
     current_app.config_from_object(this_module)
 
-    _log.debug('Celery broker host: {0}'.format(current_app.conf['BROKER_HOST']))
+    _log.debug('Celery broker host: {}'.format(current_app.conf['BROKER_HOST']))
diff --git a/mediagoblin/init/celery/from_celery.py b/mediagoblin/init/celery/from_celery.py
index b395a826..0c80a961 100644
--- a/mediagoblin/init/celery/from_celery.py
+++ b/mediagoblin/init/celery/from_celery.py
@@ -42,7 +42,7 @@ def setup_logging_from_paste_ini(loglevel, **kw):
         'PASTE_CONFIG', logging_conf_file)
 
     if not os.path.exists(logging_conf_file):
-        raise IOError('{0} does not exist. Logging can not be set up.'.format(
+        raise OSError('{} does not exist. Logging can not be set up.'.format(
             logging_conf_file))
 
     logging.config.fileConfig(logging_conf_file)
@@ -78,7 +78,7 @@ def setup_self(check_environ_for_conf=True, module_name=OUR_MODULENAME,
         mgoblin_conf_file = default_conf_file
 
     if not os.path.exists(mgoblin_conf_file):
-        raise IOError(
+        raise OSError(
             "MEDIAGOBLIN_CONFIG not set or file does not exist")
 
     # By setting the environment variable here we should ensure that
diff --git a/mediagoblin/init/config.py b/mediagoblin/init/config.py
index 2e22083a..7f9ad32a 100644
--- a/mediagoblin/init/config.py
+++ b/mediagoblin/init/config.py
@@ -149,7 +149,7 @@ def read_mediagoblin_config(config_path, config_spec_path=CONFIG_SPEC_PATH):
     return config, validation_result
 
 
-REPORT_HEADER = u"""\
+REPORT_HEADER = """\
 There were validation problems loading this config file:
 --------------------------------------------------------
 """
@@ -173,17 +173,17 @@ def generate_validation_report(config, validation_result):
         if key is not None:
             section_list.append(key)
         else:
-            section_list.append(u'[missing section]')
+            section_list.append('[missing section]')
 
-        section_string = u':'.join(section_list)
+        section_string = ':'.join(section_list)
 
         if error == False:
             # We don't care about missing values for now.
             continue
 
-        report.append(u"%s = %s" % (section_string, error))
+        report.append("{} = {}".format(section_string, error))
 
     if report:
-        return REPORT_HEADER + u"\n".join(report)
+        return REPORT_HEADER + "\n".join(report)
     else:
         return None
diff --git a/mediagoblin/listings/views.py b/mediagoblin/listings/views.py
index 4dd93543..11d480d3 100644
--- a/mediagoblin/listings/views.py
+++ b/mediagoblin/listings/views.py
@@ -45,7 +45,7 @@ def _get_tag_name_from_entries(media_entries, tag_slug):
 @uses_pagination
 def tag_listing(request, page):
     """'Gallery'/listing for this tag slug"""
-    tag_slug = request.matchdict[u'tag']
+    tag_slug = request.matchdict['tag']
 
     cursor = media_entries_for_tag_slug(request.db, tag_slug)
     cursor = cursor.order_by(MediaEntry.created.desc())
@@ -71,7 +71,7 @@ def atom_feed(request):
     """
     generates the atom feed with the tag images
     """
-    tag_slug = request.matchdict.get(u'tag')
+    tag_slug = request.matchdict.get('tag')
     feed_title = "MediaGoblin Feed"
     if tag_slug:
         feed_title += " for tag '%s'" % tag_slug
@@ -81,7 +81,7 @@ def atom_feed(request):
     else: # all recent item feed
         feed_title += " for all recent items"
         link = request.urlgen('index', qualified=True)
-        cursor = MediaEntry.query.filter_by(state=u'processed')
+        cursor = MediaEntry.query.filter_by(state='processed')
     cursor = cursor.order_by(MediaEntry.created.desc())
     cursor = cursor.limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS)
 
@@ -110,7 +110,7 @@ def atom_feed(request):
         # Include a thumbnail image in content.
         file_urls = get_media_file_paths(entry.media_files, request.urlgen)
         if 'thumb' in file_urls:
-            content = u' {desc}'.format(
+            content = ' {desc}'.format(
                 thumb=file_urls['thumb'], desc=entry.description_html)
         else:
             content = entry.description_html
diff --git a/mediagoblin/meddleware/__init__.py b/mediagoblin/meddleware/__init__.py
index 886c9ad9..093f60cf 100644
--- a/mediagoblin/meddleware/__init__.py
+++ b/mediagoblin/meddleware/__init__.py
@@ -19,7 +19,7 @@ ENABLED_MEDDLEWARE = [
     ]
 
 
-class BaseMeddleware(object):
+class BaseMeddleware:
 
     def __init__(self, mg_app):
         self.app = mg_app
diff --git a/mediagoblin/meddleware/csrf.py b/mediagoblin/meddleware/csrf.py
index 9f64f363..acde2852 100644
--- a/mediagoblin/meddleware/csrf.py
+++ b/mediagoblin/meddleware/csrf.py
@@ -116,7 +116,7 @@ class CsrfMeddleware(BaseMeddleware):
     def _make_token(self, request):
         """Generate a new token to use for CSRF protection."""
 
-        return "%s" % (getrandbits(self.CSRF_KEYLEN),)
+        return "{}".format(getrandbits(self.CSRF_KEYLEN))
 
     def verify_tokens(self, request):
         """Verify that the CSRF Cookie exists and that it matches the
diff --git a/mediagoblin/media_types/__init__.py b/mediagoblin/media_types/__init__.py
index 9f6043e9..17f520a3 100644
--- a/mediagoblin/media_types/__init__.py
+++ b/mediagoblin/media_types/__init__.py
@@ -39,7 +39,7 @@ class MissingComponents(FileTypeNotSupported):
     pass
 
 
-class MediaManagerBase(object):
+class MediaManagerBase:
     "Base class for all media managers"
 
     # Please override in actual media managers
@@ -68,13 +68,13 @@ def sniff_media_contents(media_file, filename):
     '''
     media_type = hook_handle('sniff_handler', media_file, filename)
     if media_type:
-        _log.info('{0} accepts the file'.format(media_type))
+        _log.info('{} accepts the file'.format(media_type))
         return media_type, hook_handle(('media_manager', media_type))
     else:
-        _log.debug('{0} did not accept the file'.format(media_type))
+        _log.debug('{} did not accept the file'.format(media_type))
         raise FileTypeNotSupported(
             # TODO: Provide information on which file types are supported
-            _(u'Sorry, I don\'t support that file type :('))
+            _('Sorry, I don\'t support that file type :('))
 
 def get_media_type_and_manager(filename):
     '''
@@ -93,11 +93,11 @@ def get_media_type_and_manager(filename):
         if hook_handle('get_media_type_and_manager', ext[1:]):
             return hook_handle('get_media_type_and_manager', ext[1:])
     else:
-        _log.info('File {0} has no file extension, let\'s hope the sniffers get it.'.format(
+        _log.info('File {} has no file extension, let\'s hope the sniffers get it.'.format(
             filename))
 
     raise TypeNotFound(
-        _(u'Sorry, I don\'t support that file type :('))
+        _('Sorry, I don\'t support that file type :('))
 
 def type_match_handler(media_file, filename):
     '''Check media file by name and then by content
@@ -129,11 +129,11 @@ def type_match_handler(media_file, filename):
                 _log.debug(e)
                 raise
         else:
-            _log.info('No plugins handled extension {0}'.format(ext))
+            _log.info('No plugins handled extension {}'.format(ext))
     else:
-        _log.info('File {0} has no known file extension, let\'s hope '
+        _log.info('File {} has no known file extension, let\'s hope '
                 'the sniffers get it.'.format(filename))
-    raise TypeNotFound(_(u'Sorry, I don\'t support that file type :('))
+    raise TypeNotFound(_('Sorry, I don\'t support that file type :('))
 
 
 def sniff_media(media_file, filename):
diff --git a/mediagoblin/media_types/ascii/asciitoimage.py b/mediagoblin/media_types/ascii/asciitoimage.py
index 786941f6..0bd72fe4 100644
--- a/mediagoblin/media_types/ascii/asciitoimage.py
+++ b/mediagoblin/media_types/ascii/asciitoimage.py
@@ -29,7 +29,7 @@ import os
 _log = logging.getLogger(__name__)
 
 
-class AsciiToImage(object):
+class AsciiToImage:
     '''
     Converter of ASCII art into image files, preserving whitespace
 
@@ -51,7 +51,7 @@ class AsciiToImage(object):
             self._font_size,
             encoding='unic')
 
-        _log.info('Font set to {0}, size {1}'.format(
+        _log.info('Font set to {}, size {}'.format(
                 self._font,
                 self._font_size))
 
@@ -68,7 +68,7 @@ class AsciiToImage(object):
 
         # PIL's Image.save will handle both file-likes and paths
         if im.save(destination):
-            _log.info('Saved image in {0}'.format(
+            _log.info('Saved image in {}'.format(
                     destination))
 
     def _create_image(self, text):
@@ -93,7 +93,7 @@ class AsciiToImage(object):
             max(line_lengths) * self._if_dims[0],
             len(line_lengths) * self._if_dims[1])
 
-        _log.info('Destination image dimensions will be {0}'.format(
+        _log.info('Destination image dimensions will be {}'.format(
                 im_dims))
 
         im = Image.new(
@@ -108,14 +108,14 @@ class AsciiToImage(object):
         for line in lines:
             line_length = len(line)
 
-            _log.debug('Writing line at {0}'.format(char_pos))
+            _log.debug('Writing line at {}'.format(char_pos))
 
             for _pos in range(0, line_length):
                 char = line[_pos]
 
                 px_pos = self._px_pos(char_pos)
 
-                _log.debug('Writing character "{0}" at {1} (px pos {2})'.format(
+                _log.debug('Writing character "{}" at {} (px pos {})'.format(
                         char.encode('ascii', 'replace'),
                         char_pos,
                         px_pos))
diff --git a/mediagoblin/media_types/ascii/processing.py b/mediagoblin/media_types/ascii/processing.py
index 00d04e63..3c24360d 100644
--- a/mediagoblin/media_types/ascii/processing.py
+++ b/mediagoblin/media_types/ascii/processing.py
@@ -39,7 +39,7 @@ MEDIA_TYPE = 'mediagoblin.media_types.ascii'
 
 
 def sniff_handler(media_file, filename):
-    _log.info('Sniffing {0}'.format(MEDIA_TYPE))
+    _log.info('Sniffing {}'.format(MEDIA_TYPE))
 
     name, ext = os.path.splitext(filename)
     clean_ext = ext[1:].lower()
@@ -87,7 +87,7 @@ class CommonAsciiProcessor(MediaProcessor):
         else:
             self.charset = d_charset['encoding']
 
-        _log.info('Charset detected: {0}\nWill interpret as: {1}'.format(
+        _log.info('Charset detected: {}\nWill interpret as: {}'.format(
                   d_charset,
                   self.charset))
 
@@ -106,7 +106,7 @@ class CommonAsciiProcessor(MediaProcessor):
                 # Encode the unicode instance to ASCII and replace any
                 # non-ASCII with an HTML entity (&#
                 unicode_file.write(
-                    six.text_type(orig_file.read().decode(
+                    str(orig_file.read().decode(
                             self.charset)).encode(
                                 'ascii',
                                 'xmlcharrefreplace'))
@@ -270,6 +270,6 @@ class Resizer(CommonAsciiProcessor):
 
 class AsciiProcessingManager(ProcessingManager):
     def __init__(self):
-        super(AsciiProcessingManager, self).__init__()
+        super().__init__()
         self.add_processor(InitialProcessor)
         self.add_processor(Resizer)
diff --git a/mediagoblin/media_types/audio/audiotospectrogram.py b/mediagoblin/media_types/audio/audiotospectrogram.py
index 2d2f9423..5ad89b93 100644
--- a/mediagoblin/media_types/audio/audiotospectrogram.py
+++ b/mediagoblin/media_types/audio/audiotospectrogram.py
@@ -282,7 +282,7 @@ if __name__ == "__main__":
         sys.stdout.flush()
 
     if not (len(sys.argv) == 2 or len(sys.argv) == 3):
-        print("Usage:\n{0} input_file [output_file]".format(sys.argv[0]))
+        print("Usage:\n{} input_file [output_file]".format(sys.argv[0]))
         exit()
 
     audioFile = sys.argv[1]
@@ -292,6 +292,6 @@ if __name__ == "__main__":
     else:
         outputFile = 'spectrogram.png'
 
-    sys.stdout.write("Input    : {0}\nOutput   : {1}\n".format(audioFile, outputFile))
+    sys.stdout.write("Input    : {}\nOutput   : {}\n".format(audioFile, outputFile))
     drawSpectrogram(audioFile, outputFile, progressCallback = printProgress)
     sys.stdout.write("\nDone!\n")
diff --git a/mediagoblin/media_types/audio/processing.py b/mediagoblin/media_types/audio/processing.py
index 427309de..e2dc8937 100644
--- a/mediagoblin/media_types/audio/processing.py
+++ b/mediagoblin/media_types/audio/processing.py
@@ -37,11 +37,11 @@ MEDIA_TYPE = 'mediagoblin.media_types.audio'
 
 
 def sniff_handler(media_file, filename):
-    _log.info('Sniffing {0}'.format(MEDIA_TYPE))
+    _log.info('Sniffing {}'.format(MEDIA_TYPE))
     try:
         data = discover(media_file.name)
     except Exception as e:
-        _log.info(six.text_type(e))
+        _log.info(str(e))
         return None
     if data and data.get_audio_streams() and not data.get_video_streams():
         return MEDIA_TYPE
@@ -361,7 +361,7 @@ class Transcoder(CommonAudioProcessor):
 
 class AudioProcessingManager(ProcessingManager):
     def __init__(self):
-        super(AudioProcessingManager, self).__init__()
+        super().__init__()
         self.add_processor(InitialProcessor)
         self.add_processor(Resizer)
         self.add_processor(Transcoder)
diff --git a/mediagoblin/media_types/audio/transcoders.py b/mediagoblin/media_types/audio/transcoders.py
index 11ecf163..308e16d3 100644
--- a/mediagoblin/media_types/audio/transcoders.py
+++ b/mediagoblin/media_types/audio/transcoders.py
@@ -43,9 +43,9 @@ gi.require_version('Gst', '1.0')
 from gi.repository import GObject, Gst
 Gst.init(None)
 
-class Python3AudioThumbnailer(object):
+class Python3AudioThumbnailer:
     def __init__(self):
-        _log.info('Initializing {0}'.format(self.__class__.__name__))
+        _log.info('Initializing {}'.format(self.__class__.__name__))
 
     def spectrogram(self, src, dst, **kw):
         from mediagoblin.media_types.audio import audiotospectrogram
@@ -83,9 +83,9 @@ class Python3AudioThumbnailer(object):
 
 AudioThumbnailer = Python3AudioThumbnailer
 
-class AudioTranscoder(object):
+class AudioTranscoder:
     def __init__(self):
-        _log.info('Initializing {0}'.format(self.__class__.__name__))
+        _log.info('Initializing {}'.format(self.__class__.__name__))
 
         # Instantiate MainLoop
         self._loop = GObject.MainLoop()
@@ -96,10 +96,10 @@ class AudioTranscoder(object):
         def _on_pad_added(element, pad, connect_to):
             caps = pad.query_caps(None)
             name = caps.to_string()
-            _log.debug('on_pad_added: {0}'.format(name))
+            _log.debug('on_pad_added: {}'.format(name))
             if name.startswith('audio') and not connect_to.is_linked():
                 pad.link(connect_to)
-        _log.info('Transcoding {0} into {1}'.format(src, dst))
+        _log.info('Transcoding {} into {}'.format(src, dst))
         self.__on_progress = progress_callback
         # Set up pipeline
         tolerance = 80000000
@@ -155,7 +155,7 @@ class AudioTranscoder(object):
             (success, percent) = structure.get_int('percent')
             if self.__on_progress and success:
                 self.__on_progress(percent)
-            _log.info('{0}% done...'.format(percent))
+            _log.info('{}% done...'.format(percent))
         elif message.type == Gst.MessageType.EOS:
             _log.info('Done')
             self.halt()
diff --git a/mediagoblin/media_types/blog/lib.py b/mediagoblin/media_types/blog/lib.py
index b6e3dc06..45c1588f 100644
--- a/mediagoblin/media_types/blog/lib.py
+++ b/mediagoblin/media_types/blog/lib.py
@@ -24,15 +24,15 @@ def check_blog_slug_used(author_id, slug, ignore_b_id=None):
     return does_exist
     
 def may_edit_blogpost(request, blog):
-    if request.user.has_privilege(u'admin') or request.user.id == blog.author:
+    if request.user.has_privilege('admin') or request.user.id == blog.author:
         return True
     return False
 
 def set_blogpost_state(request, blogpost):
     if request.form['status'] == 'Publish':
-        blogpost.state = u'processed'
+        blogpost.state = 'processed'
     else:
-        blogpost.state = u'failed'
+        blogpost.state = 'failed'
 
 def get_all_blogposts_of_blog(request, blog, state=None):
     blog_posts_list = []
diff --git a/mediagoblin/media_types/blog/models.py b/mediagoblin/media_types/blog/models.py
index 83f520c7..6bb5e076 100644
--- a/mediagoblin/media_types/blog/models.py
+++ b/mediagoblin/media_types/blog/models.py
@@ -48,7 +48,7 @@ class Blog(Base, BlogMixin):
 
     @property
     def slug_or_id(self):
-        return (self.slug or u'blog_{0}'.format(self.id))
+        return (self.slug or 'blog_{}'.format(self.id))
  
     def get_all_blog_posts(self, state=None):
         blog_posts = Session.query(MediaEntry).join(BlogPostData)\
@@ -63,7 +63,7 @@ class Blog(Base, BlogMixin):
             post.delete(del_orphan_tags=False, commit=False)
         from mediagoblin.db.util import clean_orphan_tags
         clean_orphan_tags(commit=False)
-        super(Blog, self).delete(**kwargs)
+        super().delete(**kwargs)
         
         
     
diff --git a/mediagoblin/media_types/blog/views.py b/mediagoblin/media_types/blog/views.py
index 2bf2e5be..00c583b2 100644
--- a/mediagoblin/media_types/blog/views.py
+++ b/mediagoblin/media_types/blog/views.py
@@ -76,8 +76,8 @@ def blog_edit(request):
             if request.method=='POST' and form.validate():
                 _log.info("Here")
                 blog = request.db.Blog()
-                blog.title = six.text_type(form.title.data)
-                blog.description = six.text_type(cleaned_markdown_conversion((form.description.data)))
+                blog.title = str(form.title.data)
+                blog.description = str(cleaned_markdown_conversion(form.description.data))
                 blog.author = request.user.id
                 blog.generate_slug()
 
@@ -115,8 +115,8 @@ def blog_edit(request):
                      'app_config': mg_globals.app_config})
         else:
             if request.method == 'POST' and form.validate():
-                blog.title = six.text_type(form.title.data)
-                blog.description = six.text_type(cleaned_markdown_conversion((form.description.data)))
+                blog.title = str(form.title.data)
+                blog.description = str(cleaned_markdown_conversion(form.description.data))
                 blog.author = request.user.id
                 blog.generate_slug()
 
@@ -143,10 +143,10 @@ def blogpost_create(request):
 
         blogpost = request.db.MediaEntry()
         blogpost.media_type = 'mediagoblin.media_types.blogpost'
-        blogpost.title = six.text_type(form.title.data)
-        blogpost.description = six.text_type(cleaned_markdown_conversion((form.description.data)))
+        blogpost.title = str(form.title.data)
+        blogpost.description = str(cleaned_markdown_conversion(form.description.data))
         blogpost.tags =  convert_to_tag_list_of_dicts(form.tags.data)
-        blogpost.license = six.text_type(form.license.data) or None
+        blogpost.license = str(form.license.data) or None
         blogpost.actor = request.user.id
         blogpost.generate_slug()
 
@@ -196,10 +196,10 @@ def blogpost_edit(request):
 
     form = blog_forms.BlogPostEditForm(request.form, **defaults)
     if request.method == 'POST' and form.validate():
-        blogpost.title = six.text_type(form.title.data)
-        blogpost.description = six.text_type(cleaned_markdown_conversion((form.description.data)))
+        blogpost.title = str(form.title.data)
+        blogpost.description = str(cleaned_markdown_conversion(form.description.data))
         blogpost.tags =  convert_to_tag_list_of_dicts(form.tags.data)
-        blogpost.license = six.text_type(form.license.data)
+        blogpost.license = str(form.license.data)
         set_blogpost_state(request, blogpost)
         blogpost.generate_slug()
         blogpost.save()
@@ -233,7 +233,7 @@ def blog_dashboard(request, page, url_user=None):
     blogs = request.db.Blog.query.filter_by(author=url_user.id)
     config = pluginapi.get_config('mediagoblin.media_types.blog')
     max_blog_count = config['max_blog_count']
-    if request.user and (request.user.id == url_user.id or request.user.has_privilege(u'admin')):
+    if request.user and (request.user.id == url_user.id or request.user.has_privilege('admin')):
         if blog_slug:
             blog = get_blog_by_slug(request, blog_slug)
             if not blog:
@@ -276,7 +276,7 @@ def blog_post_listing(request, page, url_user=None):
     if not blog:
         return render_404(request)
 
-    all_blog_posts = blog.get_all_blog_posts(u'processed').order_by(MediaEntry.created.desc())
+    all_blog_posts = blog.get_all_blog_posts('processed').order_by(MediaEntry.created.desc())
     pagination = Pagination(page, all_blog_posts)
     pagination.per_page = 8
     blog_posts_on_a_page = pagination()
@@ -297,7 +297,7 @@ def draft_view(request):
     blog_post_slug = request.matchdict.get('blog_post_slug', None)
     user = request.matchdict.get('user')
     blog = get_blog_by_slug(request, blog_slug, author=request.user.id)
-    blogpost = request.db.MediaEntry.query.filter_by(state = u'failed', actor=request.user.id, slug=blog_post_slug).first()
+    blogpost = request.db.MediaEntry.query.filter_by(state = 'failed', actor=request.user.id, slug=blog_post_slug).first()
 
     if not blog or not blogpost:
         return render_404(request)
@@ -326,7 +326,7 @@ def blog_delete(request, **kwargs):
         return render_404(request)
 
     form = blog_forms.ConfirmDeleteForm(request.form)
-    if request.user.id == blog.author or request.user.has_privilege(u'admin'):
+    if request.user.id == blog.author or request.user.has_privilege('admin'):
         if request.method == 'POST' and form.validate():
             if form.confirm.data is True:
                 blog.delete()
@@ -345,7 +345,7 @@ def blog_delete(request, **kwargs):
                 return redirect(request, "mediagoblin.media_types.blog.blog_admin_dashboard",
                         user=request.user.username)
         else:
-            if request.user.has_privilege(u'admin'):
+            if request.user.has_privilege('admin'):
                 messages.add_message(
                     request,
                     messages.WARNING,
@@ -384,7 +384,7 @@ def blog_about_view(request):
         return render_404(request)
 
     else:
-        blog_posts_processed = blog.get_all_blog_posts(u'processed').count()
+        blog_posts_processed = blog.get_all_blog_posts('processed').count()
         return render_to_response(
                 request,
                 'mediagoblin/blog/blog_about.html',
diff --git a/mediagoblin/media_types/image/__init__.py b/mediagoblin/media_types/image/__init__.py
index 11f90ca5..f357dc88 100644
--- a/mediagoblin/media_types/image/__init__.py
+++ b/mediagoblin/media_types/image/__init__.py
@@ -32,7 +32,7 @@ class ImageMediaManager(MediaManagerBase):
     display_template = "mediagoblin/media_displays/image.html"
     default_thumb = "images/media_thumbs/image.png"
 
-    media_fetch_order = [u'medium', u'original', u'thumb']
+    media_fetch_order = ['medium', 'original', 'thumb']
 
     def get_original_date(self):
         """
diff --git a/mediagoblin/media_types/image/processing.py b/mediagoblin/media_types/image/processing.py
index 7ddf3f35..8f227edc 100644
--- a/mediagoblin/media_types/image/processing.py
+++ b/mediagoblin/media_types/image/processing.py
@@ -14,7 +14,6 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see .
 
-from __future__ import print_function
 
 try:
     from PIL import Image
@@ -69,9 +68,9 @@ def resize_image(entry, resized, keyname, target_name, new_size,
     try:
         resize_filter = PIL_FILTERS[filter.upper()]
     except KeyError:
-        raise Exception('Filter "{0}" not found, choose one of {1}'.format(
-            six.text_type(filter),
-            u', '.join(PIL_FILTERS.keys())))
+        raise Exception('Filter "{}" not found, choose one of {}'.format(
+            str(filter),
+            ', '.join(PIL_FILTERS.keys())))
 
     resized.thumbnail(new_size, resize_filter)
 
@@ -101,8 +100,8 @@ def resize_tool(entry,
     # If thumb or medium is already the same quality and size, then don't
     # reprocess
     if _skip_resizing(entry, keyname, new_size, quality, filter):
-        _log.info('{0} of same size and quality already in use, skipping '
-                  'resizing of media {1}.'.format(keyname, entry.id))
+        _log.info('{} of same size and quality already in use, skipping '
+                  'resizing of media {}.'.format(keyname, entry.id))
         return
 
     # If the size of the original file exceeds the specified size for the desized
@@ -111,14 +110,14 @@ def resize_tool(entry,
     # Also created if the file needs rotation, or if forced.
     try:
         im = Image.open(orig_file)
-    except IOError:
+    except OSError:
         raise BadMediaFail()
     if force \
         or im.size[0] > new_size[0]\
         or im.size[1] > new_size[1]\
         or exif_image_needs_rotation(exif_tags):
         resize_image(
-            entry, im, six.text_type(keyname), target_name,
+            entry, im, str(keyname), target_name,
             tuple(new_size),
             exif_tags, conversions_subdir,
             quality, filter)
@@ -154,7 +153,7 @@ SUPPORTED_FILETYPES = ['png', 'gif', 'jpg', 'jpeg', 'tiff']
 
 
 def sniff_handler(media_file, filename):
-    _log.info('Sniffing {0}'.format(MEDIA_TYPE))
+    _log.info('Sniffing {}'.format(MEDIA_TYPE))
     name, ext = os.path.splitext(filename)
     clean_ext = ext[1:].lower()  # Strip the . from ext and make lowercase
 
@@ -162,7 +161,7 @@ def sniff_handler(media_file, filename):
         _log.info('Found file extension in supported filetypes')
         return MEDIA_TYPE
     else:
-        _log.debug('Media present, extension not found in {0}'.format(
+        _log.debug('Media present, extension not found in {}'.format(
                 SUPPORTED_FILETYPES))
 
     return None
@@ -241,7 +240,7 @@ class CommonImageProcessor(MediaProcessor):
         # Extract file metadata
         try:
             im = Image.open(self.process_filename)
-        except IOError:
+        except OSError:
             raise BadMediaFail()
 
         metadata = {
@@ -426,7 +425,7 @@ class MetadataProcessing(CommonImageProcessor):
 
 class ImageProcessingManager(ProcessingManager):
     def __init__(self):
-        super(ImageProcessingManager, self).__init__()
+        super().__init__()
         self.add_processor(InitialProcessor)
         self.add_processor(Resizer)
         self.add_processor(MetadataProcessing)
diff --git a/mediagoblin/media_types/pdf/processing.py b/mediagoblin/media_types/pdf/processing.py
index ac4bab6d..3792cfbd 100644
--- a/mediagoblin/media_types/pdf/processing.py
+++ b/mediagoblin/media_types/pdf/processing.py
@@ -169,7 +169,7 @@ def check_prerequisites():
     return True
 
 def sniff_handler(media_file, filename):
-    _log.info('Sniffing {0}'.format(MEDIA_TYPE))
+    _log.info('Sniffing {}'.format(MEDIA_TYPE))
     if not check_prerequisites():
         return None
 
@@ -185,7 +185,7 @@ def create_pdf_thumb(original, thumb_filename, width, height):
     executable = where('pdftocairo')
     args = [executable, '-scale-to', str(min(width, height)),
             '-singlefile', '-png', original, thumb_filename]
-    _log.debug('calling {0}'.format(repr(' '.join(args))))
+    _log.debug('calling {}'.format(repr(' '.join(args))))
     Popen(executable=executable, args=args).wait()
 
 def pdf_info(original):
@@ -303,7 +303,7 @@ class CommonPdfProcessor(MediaProcessor):
         args = [executable, '-scale-to', str(min(thumb_size)),
                 '-singlefile', '-png', self.pdf_filename, thumb_filename]
 
-        _log.debug('calling {0}'.format(repr(' '.join(args))))
+        _log.debug('calling {}'.format(repr(' '.join(args))))
         Popen(executable=executable, args=args).wait()
 
         # since pdftocairo added '.png', we need to include it with the
@@ -355,7 +355,7 @@ class CommonPdfProcessor(MediaProcessor):
         args = [executable, '-scale-to', str(min(size)),
                 '-singlefile', '-png', self.pdf_filename, filename]
 
-        _log.debug('calling {0}'.format(repr(' '.join(args))))
+        _log.debug('calling {}'.format(repr(' '.join(args))))
         Popen(executable=executable, args=args).wait()
 
         # since pdftocairo added '.png', we need to include it with the
@@ -467,6 +467,6 @@ class Resizer(CommonPdfProcessor):
 
 class PdfProcessingManager(ProcessingManager):
     def __init__(self):
-        super(PdfProcessingManager, self).__init__()
+        super().__init__()
         self.add_processor(InitialProcessor)
         self.add_processor(Resizer)
diff --git a/mediagoblin/media_types/raw_image/processing.py b/mediagoblin/media_types/raw_image/processing.py
index 740ba2dd..68d9bffe 100644
--- a/mediagoblin/media_types/raw_image/processing.py
+++ b/mediagoblin/media_types/raw_image/processing.py
@@ -35,7 +35,7 @@ ACCEPTED_EXTENSIONS = ['nef', 'cr2']
 # The entire function have to be copied
 
 def sniff_handler(media_file, filename):
-    _log.info('Sniffing {0}'.format(MEDIA_TYPE))
+    _log.info('Sniffing {}'.format(MEDIA_TYPE))
     name, ext = os.path.splitext(filename)
     clean_ext = ext[1:].lower()  # Strip the . from ext and make lowercase
 
@@ -43,7 +43,7 @@ def sniff_handler(media_file, filename):
         _log.info('Found file extension in supported filetypes')
         return MEDIA_TYPE
     else:
-        _log.debug('Media present, extension not found in {0}'.format(
+        _log.debug('Media present, extension not found in {}'.format(
                 ACCEPTED_EXTENSIONS))
 
     return None
@@ -54,7 +54,7 @@ class InitialRawProcessor(InitialProcessor):
         """
         Pull out a full-size JPEG-preview
         """
-        super(InitialRawProcessor, self).common_setup()
+        super().common_setup()
 
         self._original_raw = self.process_filename
 
@@ -68,7 +68,7 @@ class InitialRawProcessor(InitialProcessor):
         md.previews[-1].write_to_file(
             self.process_filename.encode('utf-8'))
         self.process_filename += '.jpg'
-        _log.debug(u'Wrote new file from {0} to preview (jpg) {1}'.format(
+        _log.debug('Wrote new file from {} to preview (jpg) {}'.format(
             self._original_raw, self.process_filename))
 
         # Override the namebuilder with our new jpg-based name
@@ -77,6 +77,6 @@ class InitialRawProcessor(InitialProcessor):
 
 class RawImageProcessingManager(ProcessingManager):
     def __init__(self):
-        super(RawImageProcessingManager, self).__init__()
+        super().__init__()
         self.add_processor(InitialRawProcessor)
         self.add_processor(Resizer)
diff --git a/mediagoblin/media_types/stl/model_loader.py b/mediagoblin/media_types/stl/model_loader.py
index c1864613..60d686d2 100644
--- a/mediagoblin/media_types/stl/model_loader.py
+++ b/mediagoblin/media_types/stl/model_loader.py
@@ -22,7 +22,7 @@ class ThreeDeeParseError(Exception):
     pass
 
 
-class ThreeDee(object):
+class ThreeDee:
     """
     3D model parser base class.  Derrived classes are used for basic
     analysis of 3D models, and are not intended to be used for 3D
diff --git a/mediagoblin/media_types/stl/processing.py b/mediagoblin/media_types/stl/processing.py
index 55764aeb..b4d9fe44 100644
--- a/mediagoblin/media_types/stl/processing.py
+++ b/mediagoblin/media_types/stl/processing.py
@@ -48,7 +48,7 @@ BLEND_SCRIPT = pkg_resources.resource_filename(
 
 
 def sniff_handler(media_file, filename):
-    _log.info('Sniffing {0}'.format(MEDIA_TYPE))
+    _log.info('Sniffing {}'.format(MEDIA_TYPE))
 
     name, ext = os.path.splitext(filename)
     clean_ext = ext[1:].lower()
@@ -57,7 +57,7 @@ def sniff_handler(media_file, filename):
         _log.info('Found file extension in supported filetypes')
         return MEDIA_TYPE
     else:
-        _log.debug('Media present, extension not found in {0}'.format(
+        _log.debug('Media present, extension not found in {}'.format(
                 SUPPORTED_FILETYPES))
 
     return None
@@ -365,6 +365,6 @@ class Resizer(CommonStlProcessor):
 
 class StlProcessingManager(ProcessingManager):
     def __init__(self):
-        super(StlProcessingManager, self).__init__()
+        super().__init__()
         self.add_processor(InitialProcessor)
         self.add_processor(Resizer)
diff --git a/mediagoblin/media_types/tools.py b/mediagoblin/media_types/tools.py
index 602b9605..72b4ab16 100644
--- a/mediagoblin/media_types/tools.py
+++ b/mediagoblin/media_types/tools.py
@@ -40,7 +40,7 @@ def discover(src):
     # init before import to work around https://bugzilla.gnome.org/show_bug.cgi?id=736260
     from gi.repository import GstPbutils
 
-    _log.info('Discovering {0}...'.format(src))
-    uri = 'file://{0}'.format(src)
+    _log.info('Discovering {}...'.format(src))
+    uri = 'file://{}'.format(src)
     discoverer = GstPbutils.Discoverer.new(60 * Gst.SECOND)
     return discoverer.discover_uri(uri)
diff --git a/mediagoblin/media_types/video/__init__.py b/mediagoblin/media_types/video/__init__.py
index 0c822f69..356e0bb8 100644
--- a/mediagoblin/media_types/video/__init__.py
+++ b/mediagoblin/media_types/video/__init__.py
@@ -41,7 +41,7 @@ class VideoMediaManager(MediaManagerBase):
         video_res.remove(video_config['default_resolution'])
         video_res.insert(0, video_config['default_resolution'])
         video_res = ['webm_{}'.format(x) for x in video_res]
-        return ([u'webm_video'] + video_res + [u'original'])
+        return (['webm_video'] + video_res + ['original'])
 
 
 def get_media_type_and_manager(ext):
diff --git a/mediagoblin/media_types/video/migrations.py b/mediagoblin/media_types/video/migrations.py
index 2445cd4d..d6700f66 100644
--- a/mediagoblin/media_types/video/migrations.py
+++ b/mediagoblin/media_types/video/migrations.py
@@ -79,12 +79,12 @@ def change_metadata_format(db):
                 'videolength': 'length',
                 }
 
-        new_metadata['video'] = [dict((v, metadata.get(k))
-                for k, v in video_key_map.items() if metadata.get(k))]
-        new_metadata['audio'] = [dict((v, metadata.get(k))
-                for k, v in audio_key_map.items() if metadata.get(k))]
-        new_metadata['common'] = dict((v, metadata.get(k))
-                for k, v in common_key_map.items() if metadata.get(k))
+        new_metadata['video'] = [{v: metadata.get(k)
+                for k, v in video_key_map.items() if metadata.get(k)}]
+        new_metadata['audio'] = [{v: metadata.get(k)
+                for k, v in audio_key_map.items() if metadata.get(k)}]
+        new_metadata['common'] = {v: metadata.get(k)
+                for k, v in common_key_map.items() if metadata.get(k)}
 
         # 'mimetype' should be in tags
         new_metadata['common']['tags'] = {'mimetype': metadata.get('mimetype')}
diff --git a/mediagoblin/media_types/video/models.py b/mediagoblin/media_types/video/models.py
index da635ed7..396e35e1 100644
--- a/mediagoblin/media_types/video/models.py
+++ b/mediagoblin/media_types/video/models.py
@@ -86,7 +86,7 @@ class VideoData(Base):
             if video_codec == "vp8 video":
                 video_codec = "vp8"
 
-            return '%s; codecs="%s, %s"' % (
+            return '{}; codecs="{}, {}"'.format(
                 mimetype, video_codec, audio_codec)
         else:
             return video.VideoMediaManager.default_webm_type
diff --git a/mediagoblin/media_types/video/processing.py b/mediagoblin/media_types/video/processing.py
index 890ac688..0aa70a93 100644
--- a/mediagoblin/media_types/video/processing.py
+++ b/mediagoblin/media_types/video/processing.py
@@ -46,19 +46,19 @@ class VideoTranscodingFail(BaseProcessingFail):
     '''
     Error raised if video transcoding fails
     '''
-    general_message = _(u'Video transcoding failed')
+    general_message = _('Video transcoding failed')
 
 
 def sniffer(media_file):
     '''New style sniffer, used in two-steps check; requires to have .name'''
-    _log.info('Sniffing {0}'.format(MEDIA_TYPE))
+    _log.info('Sniffing {}'.format(MEDIA_TYPE))
     try:
         data = transcoders.discover(media_file.name)
     except Exception as e:
         # this is usually GLib.GError, but we don't really care which one
-        _log.warning(u'GStreamer: {0}'.format(six.text_type(e)))
-        raise MissingComponents(u'GStreamer: {0}'.format(six.text_type(e)))
-    _log.debug('Discovered: {0}'.format(data))
+        _log.warning('GStreamer: {}'.format(str(e)))
+        raise MissingComponents('GStreamer: {}'.format(str(e)))
+    _log.debug('Discovered: {}'.format(data))
 
     if not data.get_video_streams():
         raise MissingComponents('No video streams found in this video')
@@ -66,17 +66,17 @@ def sniffer(media_file):
     if data.get_result() != 0:  # it's 0 if success
         try:
             missing = data.get_misc().get_string('name')
-            _log.warning('GStreamer: missing {0}'.format(missing))
+            _log.warning('GStreamer: missing {}'.format(missing))
         except AttributeError as e:
             # AttributeError happens here on gstreamer >1.4, when get_misc
             # returns None. There is a special function to get info about
             # missing plugin. This info should be printed to logs for admin and
             # showed to the user in a short and nice version
             details = data.get_missing_elements_installer_details()
-            _log.warning('GStreamer: missing: {0}'.format(', '.join(details)))
-            missing = u', '.join([u'{0} ({1})'.format(*d.split('|')[3:])
+            _log.warning('GStreamer: missing: {}'.format(', '.join(details)))
+            missing = ', '.join(['{} ({})'.format(*d.split('|')[3:])
                                   for d in details])
-        raise MissingComponents(u'{0} is missing'.format(missing))
+        raise MissingComponents('{} is missing'.format(missing))
 
     return MEDIA_TYPE
 
@@ -89,13 +89,13 @@ def sniff_handler(media_file, filename):
 
     if clean_ext in EXCLUDED_EXTS:
         # We don't handle this filetype, though gstreamer might think we can
-        _log.info('Refused to process {0} due to excluded extension'.format(filename))
+        _log.info('Refused to process {} due to excluded extension'.format(filename))
         return None
 
     try:
         return sniffer(media_file)
     except:
-        _log.error('Could not discover {0}'.format(filename))
+        _log.error('Could not discover {}'.format(filename))
         return None
 
 def get_tags(stream_info):
@@ -111,7 +111,7 @@ def get_tags(stream_info):
     # date/datetime should be converted from GDate/GDateTime to strings
     if 'date' in tags:
         date = tags['date']
-        tags['date'] = "%s-%s-%s" % (
+        tags['date'] = "{}-{}-{}".format(
                 date.year, date.month, date.day)
 
     if 'datetime' in tags:
@@ -127,7 +127,7 @@ def get_tags(stream_info):
             tags['datetime'] = None
     for k, v in tags.copy().items():
         # types below are accepted by json; others must not present
-        if not isinstance(v, (dict, list, six.string_types, int, float, bool,
+        if not isinstance(v, (dict, list, (str,), int, float, bool,
                               type(None))):
             del tags[k]
     return dict(tags)
@@ -192,10 +192,10 @@ def main_task(entry_id, resolution, medium_size, **process_info):
         processor.generate_thumb(thumb_size=process_info['thumb_size'])
         processor.store_orig_metadata()
     # Make state of entry as processed
-    entry.state = u'processed'
+    entry.state = 'processed'
     entry.save()
-    _log.info(u'MediaEntry ID {0} is processed (transcoded to default'
-              ' resolution): {1}'.format(entry.id, medium_size))
+    _log.info('MediaEntry ID {} is processed (transcoded to default'
+              ' resolution): {}'.format(entry.id, medium_size))
     _log.debug('MediaEntry processed')
 
 
@@ -211,7 +211,7 @@ def complementary_task(entry_id, resolution, medium_size, **process_info):
                             vp8_quality=process_info['vp8_quality'],
                             vp8_threads=process_info['vp8_threads'],
                             vorbis_quality=process_info['vorbis_quality'])
-    _log.info(u'MediaEntry ID {0} is transcoded to {1}'.format(
+    _log.info('MediaEntry ID {} is transcoded to {}'.format(
         entry.id, medium_size))
 
 
@@ -585,7 +585,7 @@ class Transcoder(CommonVideoProcessor):
 
 class VideoProcessingManager(ProcessingManager):
     def __init__(self):
-        super(VideoProcessingManager, self).__init__()
+        super().__init__()
         self.add_processor(InitialProcessor)
         self.add_processor(Resizer)
         self.add_processor(Transcoder)
@@ -596,7 +596,7 @@ class VideoProcessingManager(ProcessingManager):
         def_res = video_config['default_resolution']
         priority_num = len(video_config['available_resolutions']) + 1
 
-        entry.state = u'processing'
+        entry.state = 'processing'
         entry.save()
 
         reprocess_info = reprocess_info or {}
diff --git a/mediagoblin/media_types/video/transcoders.py b/mediagoblin/media_types/video/transcoders.py
index b0350c76..b5fd08e6 100644
--- a/mediagoblin/media_types/video/transcoders.py
+++ b/mediagoblin/media_types/video/transcoders.py
@@ -14,7 +14,6 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see .
 
-from __future__ import division, print_function
 
 import os
 import sys
@@ -63,7 +62,7 @@ def capture_thumb(video_path, dest_path, width=None, height=None, percent=0.5):
         '''This is a callback to dynamically add element to pipeline'''
         caps = pad.query_caps(None)
         name = caps.to_string()
-        _log.debug('on_pad_added: {0}'.format(name))
+        _log.debug('on_pad_added: {}'.format(name))
         if name.startswith('video') and not connect_to.is_linked():
             pad.link(connect_to)
 
@@ -71,7 +70,7 @@ def capture_thumb(video_path, dest_path, width=None, height=None, percent=0.5):
     # ! CAPS ! appsink
     pipeline = Gst.Pipeline()
     uridecodebin = Gst.ElementFactory.make('uridecodebin', None)
-    uridecodebin.set_property('uri', 'file://{0}'.format(video_path))
+    uridecodebin.set_property('uri', 'file://{}'.format(video_path))
     videoconvert = Gst.ElementFactory.make('videoconvert', None)
     uridecodebin.connect('pad-added', pad_added,
                          videoconvert.get_static_pad('sink'))
@@ -105,7 +104,7 @@ def capture_thumb(video_path, dest_path, width=None, height=None, percent=0.5):
     # timeout of 3 seconds below was set experimentally
     state = pipeline.get_state(Gst.SECOND * 3)
     if state[0] != Gst.StateChangeReturn.SUCCESS:
-        _log.warning('state change failed, {0}'.format(state))
+        _log.warning('state change failed, {}'.format(state))
         return
 
     # get duration
@@ -115,7 +114,7 @@ def capture_thumb(video_path, dest_path, width=None, height=None, percent=0.5):
         return
 
     seek_to = int(duration * int(percent * 100) / 100)
-    _log.debug('Seeking to {0} of {1}'.format(
+    _log.debug('Seeking to {} of {}'.format(
             float(seek_to) / Gst.SECOND, float(duration) / Gst.SECOND))
     seek = pipeline.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH, seek_to)
     if not seek:
@@ -140,13 +139,13 @@ def capture_thumb(video_path, dest_path, width=None, height=None, percent=0.5):
     im = Image.frombytes('RGB', (width, height),
                          buffer.extract_dup(0, buffer.get_size()))
     im.save(dest_path)
-    _log.info('thumbnail saved to {0}'.format(dest_path))
+    _log.info('thumbnail saved to {}'.format(dest_path))
 
     # cleanup
     pipeline.set_state(Gst.State.NULL)
 
 
-class VideoTranscoder(object):
+class VideoTranscoder:
     '''
     Video transcoder
 
@@ -375,7 +374,7 @@ class VideoTranscoder(object):
                     _log.info('{percent}% of {dest} resolution done..'
                               '.'.format(percent=percent, dest=self.destination_dimensions))
         elif message.type == Gst.MessageType.ERROR:
-            _log.error('Got error: {0}'.format(message.parse_error()))
+            _log.error('Got error: {}'.format(message.parse_error()))
             self.dst_data = None
             self.__stop()
 
diff --git a/mediagoblin/media_types/video/util.py b/mediagoblin/media_types/video/util.py
index cf8dc72d..023194b5 100644
--- a/mediagoblin/media_types/video/util.py
+++ b/mediagoblin/media_types/video/util.py
@@ -43,7 +43,7 @@ def skip_transcode(metadata, size):
     # XXX: how were we supposed to use it?
     medium_config = mgg.global_config['media:medium']
 
-    _log.debug('skip_transcode config: {0}'.format(config))
+    _log.debug('skip_transcode config: {}'.format(config))
 
     metadata_tags = metadata.get_tags()
     if not metadata_tags:
diff --git a/mediagoblin/mg_globals.py b/mediagoblin/mg_globals.py
index 7da31680..4bd08ab0 100644
--- a/mediagoblin/mg_globals.py
+++ b/mediagoblin/mg_globals.py
@@ -65,7 +65,7 @@ def setup_globals(**kwargs):
     """
     from mediagoblin import mg_globals
 
-    for key, value in six.iteritems(kwargs):
+    for key, value in kwargs.items():
         if not hasattr(mg_globals, key):
             raise AssertionError("Global %s not known" % key)
         setattr(mg_globals, key, value)
diff --git a/mediagoblin/moderation/forms.py b/mediagoblin/moderation/forms.py
index e46cfd36..22f2a759 100644
--- a/mediagoblin/moderation/forms.py
+++ b/mediagoblin/moderation/forms.py
@@ -18,10 +18,10 @@ import wtforms
 from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
 
 ACTION_CHOICES = [
-    (u'takeaway', _(u'Take away privilege')),
-    (u'userban', _(u'Ban the user')),
-    (u'sendmessage', _(u'Send the user a message')),
-    (u'delete', _(u'Delete the content'))]
+    ('takeaway', _('Take away privilege')),
+    ('userban', _('Ban the user')),
+    ('sendmessage', _('Send the user a message')),
+    ('delete', _('Delete the content'))]
 
 class MultiCheckboxField(wtforms.SelectMultipleField):
     """
@@ -50,11 +50,11 @@ class BanForm(wtforms.Form):
     This form is used by an admin to ban a user directly from their user page.
     """
     user_banned_until = wtforms.DateField(
-        _(u'User will be banned until:'),
+        _('User will be banned until:'),
         format='%Y-%m-%d',
         validators=[wtforms.validators.optional()])
     why_user_was_banned = wtforms.TextAreaField(
-        _(u'Why are you banning this User?'),
+        _('Why are you banning this User?'),
         validators=[wtforms.validators.optional()])
 
 # =========== Forms for mediagoblin.moderation.report page =================  #
@@ -106,26 +106,26 @@ class ReportResolutionForm(wtforms.Form):
                                         -ved the report in such a way.
     """
     action_to_resolve = MultiCheckboxField(
-        _(u'What action will you take to resolve the report?'),
+        _('What action will you take to resolve the report?'),
         validators=[wtforms.validators.optional()],
         choices=ACTION_CHOICES)
     targeted_user   = wtforms.HiddenField('',
         validators=[wtforms.validators.required()])
     take_away_privileges = wtforms.SelectMultipleField(
-        _(u'What privileges will you take away?'),
+        _('What privileges will you take away?'),
         validators=[wtforms.validators.optional()])
     user_banned_until = wtforms.DateField(
-        _(u'User will be banned until:'),
+        _('User will be banned until:'),
         format='%Y-%m-%d',
         validators=[wtforms.validators.optional()])
     why_user_was_banned = wtforms.TextAreaField(
-        _(u'Why user was banned:'),
+        _('Why user was banned:'),
         validators=[wtforms.validators.optional()])
     message_to_user = wtforms.TextAreaField(
-        _(u'Message to user:'),
+        _('Message to user:'),
         validators=[wtforms.validators.optional()])
     resolution_content = wtforms.TextAreaField(
-        _(u'Resolution content:'))
+        _('Resolution content:'))
 
 # ======== Forms for mediagoblin.moderation.report_panel page ==============  #
 
diff --git a/mediagoblin/moderation/tools.py b/mediagoblin/moderation/tools.py
index 36d89d71..2e85a238 100644
--- a/mediagoblin/moderation/tools.py
+++ b/mediagoblin/moderation/tools.py
@@ -30,24 +30,24 @@ def take_punitive_actions(request, form, report, user):
 
     # The bulk of this action is running through all of the different
     # punitive actions that a moderator could take.
-    if u'takeaway' in form.action_to_resolve.data:
+    if 'takeaway' in form.action_to_resolve.data:
         for privilege_name in form.take_away_privileges.data:
             take_away_privileges(user.username, privilege_name)
             form.resolution_content.data += \
-                _(u"\n{mod} took away {user}\'s {privilege} privileges.").format(
+                _("\n{mod} took away {user}\'s {privilege} privileges.").format(
                     mod=request.user.username,
                     user=user.username,
                     privilege=privilege_name)
 
     # If the moderator elects to ban the user, a new instance of user_ban
     # will be created.
-    if u'userban' in form.action_to_resolve.data:
+    if 'userban' in form.action_to_resolve.data:
         user_ban = ban_user(form.targeted_user.data,
             expiration_date=form.user_banned_until.data,
             reason=form.why_user_was_banned.data)
         Session.add(user_ban)
         form.resolution_content.data += \
-            _(u"\n{mod} banned user {user} {expiration_date}.").format(
+            _("\n{mod} banned user {user} {expiration_date}.").format(
                 mod=request.user.username,
                 user=user.username,
                 expiration_date = (
@@ -59,26 +59,26 @@ def take_punitive_actions(request, form, report, user):
 
     # If the moderator elects to send a warning message. An email will be
     # sent to the email address given at sign up
-    if u'sendmessage' in form.action_to_resolve.data:
+    if 'sendmessage' in form.action_to_resolve.data:
         message_body = form.message_to_user.data
         form.resolution_content.data += \
-            _(u"\n{mod} sent a warning email to the {user}.").format(
+            _("\n{mod} sent a warning email to the {user}.").format(
                 mod=request.user.username,
                 user=user.username)
 
-    if u'delete' in form.action_to_resolve.data and \
+    if 'delete' in form.action_to_resolve.data and \
         report.is_comment_report():
             deleted_comment = report.obj()
             deleted_comment.delete()
             form.resolution_content.data += \
-                _(u"\n{mod} deleted the comment.").format(
+                _("\n{mod} deleted the comment.").format(
                     mod=request.user.username)
-    elif u'delete' in form.action_to_resolve.data and \
+    elif 'delete' in form.action_to_resolve.data and \
         report.is_media_entry_report():
             deleted_media = report.obj()
             deleted_media.delete()
             form.resolution_content.data += \
-                _(u"\n{mod} deleted the media entry.").format(
+                _("\n{mod} deleted the media entry.").format(
                     mod=request.user.username)
     report.archive(
         resolver_id=request.user.id,
@@ -216,7 +216,7 @@ def parse_report_panel_settings(form):
         filters['reported_user_id'] = form.reported_user.data
         filters['reporter_id'] = form.reporter.data
 
-    filters = dict((k, v)
-        for k, v in six.iteritems(filters) if v)
+    filters = {k: v
+        for k, v in filters.items() if v}
 
     return filters
diff --git a/mediagoblin/moderation/views.py b/mediagoblin/moderation/views.py
index ca0e6533..90a7e95f 100644
--- a/mediagoblin/moderation/views.py
+++ b/mediagoblin/moderation/views.py
@@ -32,14 +32,14 @@ def moderation_media_processing_panel(request):
     '''
     Show the global media processing panel for this instance
     '''
-    processing_entries = MediaEntry.query.filter_by(state = u'processing').\
+    processing_entries = MediaEntry.query.filter_by(state = 'processing').\
         order_by(MediaEntry.created.desc())
 
     # Get media entries which have failed to process
-    failed_entries = MediaEntry.query.filter_by(state = u'failed').\
+    failed_entries = MediaEntry.query.filter_by(state = 'failed').\
         order_by(MediaEntry.created.desc())
 
-    processed_entries = MediaEntry.query.filter_by(state = u'processed').\
+    processed_entries = MediaEntry.query.filter_by(state = 'processed').\
         order_by(MediaEntry.created.desc()).limit(10)
 
     # Render to response
@@ -163,8 +163,8 @@ def moderation_reports_detail(request):
     ]
 
     if request.method == "POST" and form.validate() and not (
-        not request.user.has_privilege(u'admin') and
-        report.reported_user.has_privilege(u'admin')):
+        not request.user.has_privilege('admin') and
+        report.reported_user.has_privilege('admin')):
 
         user = User.query.get(form.targeted_user.data)
         return take_punitive_actions(request, form, report, user)
@@ -178,7 +178,7 @@ def moderation_reports_detail(request):
         {'report':report,
          'form':form})
 
-@user_has_privilege(u'admin')
+@user_has_privilege('admin')
 @active_user_from_url
 def give_or_take_away_privilege(request, url_user):
     '''
@@ -200,7 +200,7 @@ def give_or_take_away_privilege(request, url_user):
         'mediagoblin.moderation.users_detail',
         user=url_user.username)
 
-@user_has_privilege(u'admin')
+@user_has_privilege('admin')
 @active_user_from_url
 def ban_or_unban(request, url_user):
     """
diff --git a/mediagoblin/notifications/__init__.py b/mediagoblin/notifications/__init__.py
index d554de2d..96c0276a 100644
--- a/mediagoblin/notifications/__init__.py
+++ b/mediagoblin/notifications/__init__.py
@@ -90,7 +90,7 @@ def mark_comment_notification_seen(comment_id, user):
         object_id=comment_gmr.id
     ).first()
 
-    _log.debug(u'Marking {0} as seen.'.format(notification))
+    _log.debug('Marking {} as seen.'.format(notification))
 
     mark_notification_seen(notification)
 
diff --git a/mediagoblin/notifications/task.py b/mediagoblin/notifications/task.py
index 652b78e2..74d67569 100644
--- a/mediagoblin/notifications/task.py
+++ b/mediagoblin/notifications/task.py
@@ -35,7 +35,7 @@ class EmailNotificationTask(Task):
     '''
     def run(self, notification_id, message):
         cn = Notification.query.filter_by(id=notification_id).first()
-        _log.info(u'Sending notification email about {0}'.format(cn))
+        _log.info('Sending notification email about {}'.format(cn))
 
         return send_email(
             message['from'],
diff --git a/mediagoblin/oauth/oauth.py b/mediagoblin/oauth/oauth.py
index cdd8c842..64ada12b 100644
--- a/mediagoblin/oauth/oauth.py
+++ b/mediagoblin/oauth/oauth.py
@@ -27,7 +27,7 @@ class GMGRequestValidator(RequestValidator):
 
     def __init__(self, data=None, *args, **kwargs):
         self.POST = data
-        super(GMGRequestValidator, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
 
     def check_nonce(self, nonce):
         """
@@ -43,15 +43,15 @@ class GMGRequestValidator(RequestValidator):
 
     def save_request_token(self, token, request):
         """ Saves request token in db """
-        client_id = self.POST[u"oauth_consumer_key"]
+        client_id = self.POST["oauth_consumer_key"]
 
         request_token = RequestToken(
             token=token["oauth_token"],
             secret=token["oauth_token_secret"],
         )
         request_token.client = client_id
-        if u"oauth_callback" in self.POST:
-            request_token.callback = self.POST[u"oauth_callback"]
+        if "oauth_callback" in self.POST:
+            request_token.callback = self.POST["oauth_callback"]
         request_token.save()
 
     def save_verifier(self, token, verifier, request):
@@ -188,4 +188,4 @@ class GMGRequest(Request):
         kwargs["body"] = kwargs.get("body", request.data)
         kwargs["headers"] = kwargs.get("headers", dict(request.headers))
 
-        super(GMGRequest, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
diff --git a/mediagoblin/oauth/views.py b/mediagoblin/oauth/views.py
index ef91eb91..97426f22 100644
--- a/mediagoblin/oauth/views.py
+++ b/mediagoblin/oauth/views.py
@@ -50,7 +50,7 @@ def client_register(request):
         error = "Could not decode data."
         return json_response({"error": error}, status=400)
 
-    if data is "":
+    if data == "":
         error = "Unknown Content-Type"
         return json_response({"error": error}, status=400)
 
@@ -128,7 +128,7 @@ def client_register(request):
 
     logo_uri = data.get("logo_uri", client.logo_url)
     if logo_uri is not None and not validate_url(logo_uri):
-        error = "Logo URI {0} is not a valid URI.".format(logo_uri)
+        error = "Logo URI {} is not a valid URI.".format(logo_uri)
         return json_response(
                 {"error": error},
                 status=400
@@ -140,7 +140,7 @@ def client_register(request):
 
     contacts = data.get("contacts", None)
     if contacts is not None:
-        if not isinstance(contacts, six.text_type):
+        if not isinstance(contacts, str):
             error = "Contacts must be a string of space-seporated email addresses."
             return json_response({"error": error}, status=400)
 
@@ -148,7 +148,7 @@ def client_register(request):
         for contact in contacts:
             if not validate_email(contact):
                 # not a valid email
-                error = "Email {0} is not a valid email.".format(contact)
+                error = "Email {} is not a valid email.".format(contact)
                 return json_response({"error": error}, status=400)
 
 
@@ -156,7 +156,7 @@ def client_register(request):
 
     redirect_uris = data.get("redirect_uris", None)
     if redirect_uris is not None:
-        if not isinstance(redirect_uris, six.text_type):
+        if not isinstance(redirect_uris, str):
             error = "redirect_uris must be space-seporated URLs."
             return json_response({"error": error}, status=400)
 
@@ -165,7 +165,7 @@ def client_register(request):
         for uri in redirect_uris:
             if not validate_url(uri):
                 # not a valid uri
-                error = "URI {0} is not a valid URI".format(uri)
+                error = "URI {} is not a valid URI".format(uri)
                 return json_response({"error": error}, status=400)
 
         client.redirect_uri = redirect_uris
@@ -198,12 +198,12 @@ def request_token(request):
 
     authorization = decode_authorization_header(data)
 
-    if authorization == dict() or u"oauth_consumer_key" not in authorization:
+    if authorization == dict() or "oauth_consumer_key" not in authorization:
         error = "Missing required parameter."
         return json_response({"error": error}, status=400)
 
     # check the client_id
-    client_id = authorization[u"oauth_consumer_key"]
+    client_id = authorization["oauth_consumer_key"]
     client = Client.query.filter_by(id=client_id).first()
 
     if client == None:
@@ -217,8 +217,8 @@ def request_token(request):
     tokens = rv.create_request_token(request, authorization)
 
     # store the nonce & timestamp before we return back
-    nonce = authorization[u"oauth_nonce"]
-    timestamp = authorization[u"oauth_timestamp"]
+    nonce = authorization["oauth_nonce"]
+    timestamp = authorization["oauth_timestamp"]
     timestamp = datetime.datetime.fromtimestamp(float(timestamp))
 
     nc = NonceTimestamp(nonce=nonce, timestamp=timestamp)
@@ -309,7 +309,7 @@ def authorize_finish(request):
                 )
 
     # okay we need to redirect them then!
-    querystring = "?oauth_token={0}&oauth_verifier={1}".format(
+    querystring = "?oauth_token={}&oauth_verifier={}".format(
             oauth_request.token,
             oauth_request.verifier
             )
diff --git a/mediagoblin/plugins/api/__init__.py b/mediagoblin/plugins/api/__init__.py
index 1eddd9e0..51e22e85 100644
--- a/mediagoblin/plugins/api/__init__.py
+++ b/mediagoblin/plugins/api/__init__.py
@@ -28,7 +28,7 @@ def setup_plugin():
 
     config = pluginapi.get_config(__name__)
 
-    _log.debug('API config: {0}'.format(config))
+    _log.debug('API config: {}'.format(config))
 
     routes = [
         ('mediagoblin.plugins.api.test',
diff --git a/mediagoblin/plugins/api/tools.py b/mediagoblin/plugins/api/tools.py
index e406888e..eff9009d 100644
--- a/mediagoblin/plugins/api/tools.py
+++ b/mediagoblin/plugins/api/tools.py
@@ -30,7 +30,7 @@ from mediagoblin.storage.filestorage import BasicFileStorage
 _log = logging.getLogger(__name__)
 
 
-class Auth(object):
+class Auth:
     '''
     An object with two significant methods, 'trigger' and 'run'.
 
@@ -115,7 +115,7 @@ def api_auth(controller):
 
         for auth in PluginManager().get_hook_callables('auth'):
             if auth.trigger(request):
-                _log.debug('{0} believes it is capable of authenticating this request.'.format(auth))
+                _log.debug('{} believes it is capable of authenticating this request.'.format(auth))
                 auth_candidates.append(auth)
 
         # If we can't find any authentication methods, we should not let them
@@ -126,7 +126,7 @@ def api_auth(controller):
         # For now, just select the first one in the list
         auth = auth_candidates[0]
 
-        _log.debug('Using {0} to authorize request {1}'.format(
+        _log.debug('Using {} to authorize request {}'.format(
             auth, request.url))
 
         if not auth(request, *args, **kw):
diff --git a/mediagoblin/plugins/api/views.py b/mediagoblin/plugins/api/views.py
index fdd22ace..84f919b4 100644
--- a/mediagoblin/plugins/api/views.py
+++ b/mediagoblin/plugins/api/views.py
@@ -54,16 +54,16 @@ def post_entry(request):
 
     callback_url = request.form.get('callback_url')
     if callback_url:
-        callback_url = six.text_type(callback_url)
+        callback_url = str(callback_url)
     try:
         entry = submit_media(
             mg_app=request.app, user=request.user,
             submitted_file=request.files['file'],
             filename=request.files['file'].filename,
-            title=six.text_type(request.form.get('title')),
-            description=six.text_type(request.form.get('description')),
-            license=six.text_type(request.form.get('license', '')),
-            tags_string=six.text_type(request.form.get('tags', '')),
+            title=str(request.form.get('title')),
+            description=str(request.form.get('description')),
+            license=str(request.form.get('license', '')),
+            tags_string=str(request.form.get('tags', '')),
             callback_url=callback_url)
 
         return json_response(get_entry_serializable(entry, request.urlgen))
@@ -71,7 +71,7 @@ def post_entry(request):
     # Handle upload limit issues
     except FileUploadLimit:
         raise BadRequest(
-            _(u'Sorry, the file size is too big.'))
+            _('Sorry, the file size is too big.'))
     except UserUploadLimit:
         raise BadRequest(
             _('Sorry, uploading this file will put you over your'
@@ -99,7 +99,7 @@ def get_entries(request):
     entries = request.db.MediaEntry.query
 
     # TODO: Make it possible to fetch unprocessed media, or media in-processing
-    entries = entries.filter_by(state=u'processed')
+    entries = entries.filter_by(state='processed')
 
     # TODO: Add sort order customization
     entries = entries.order_by(request.db.MediaEntry.created.desc())
diff --git a/mediagoblin/plugins/archivalook/models.py b/mediagoblin/plugins/archivalook/models.py
index 3d8dd756..a7f72642 100644
--- a/mediagoblin/plugins/archivalook/models.py
+++ b/mediagoblin/plugins/archivalook/models.py
@@ -64,17 +64,17 @@ class FeaturedMedia(Base):
         self.save()
 
     def demote(self):
-        if self.is_last_of_type() and self.display_type == u'primary':
-            self.display_type = u'secondary'
-        elif self.is_last_of_type() and self.display_type == u'secondary':
-            self.display_type = u'tertiary'
+        if self.is_last_of_type() and self.display_type == 'primary':
+            self.display_type = 'secondary'
+        elif self.is_last_of_type() and self.display_type == 'secondary':
+            self.display_type = 'tertiary'
         self.save()
 
     def promote(self):
-        if self.is_first_of_type() and self.display_type == u'secondary':
-            self.display_type = u'primary'
-        elif self.is_first_of_type() and self.display_type == u'tertiary':
-            self.display_type = u'secondary'
+        if self.is_first_of_type() and self.display_type == 'secondary':
+            self.display_type = 'primary'
+        elif self.is_first_of_type() and self.display_type == 'tertiary':
+            self.display_type = 'secondary'
         self.save()
 
     def is_first_of_type(self):
diff --git a/mediagoblin/plugins/archivalook/tools.py b/mediagoblin/plugins/archivalook/tools.py
index ad2eee5f..47eec7dc 100644
--- a/mediagoblin/plugins/archivalook/tools.py
+++ b/mediagoblin/plugins/archivalook/tools.py
@@ -56,7 +56,7 @@ def parse_url(url):
                                         who uploaded the piece of media, slug is
                                         the media entry's url slug.
     """
-    url = six.text_type(url)
+    url = str(url)
     u_end, m_start, m_end, end = (url.find('/u/') + 3,
                                   url.find('/m/'),
                                   url.find('/m/') + 3,
@@ -87,14 +87,14 @@ def split_featured_media_list(featured_media):
                                                 or tertiary)
     """
 
-    featured_media = six.text_type(featured_media)
+    featured_media = str(featured_media)
     featured_media_list = featured_media.split("\n")
     display_type = 0
     media_already_featured = []
     all_featured_media = []
     for line in featured_media_list:
         if line == '' or line.isspace(): continue
-        elif line.startswith(u'-'):
+        elif line.startswith('-'):
             display_type += 1
         elif display_type <= 0 or display_type > 3: continue
         else:
@@ -106,9 +106,9 @@ def split_featured_media_list(featured_media):
             media_already_featured.append(media)
             all_featured_media.append((media,
                 [None,
-                u'primary',
-                u'secondary',
-                u'tertiary'][display_type]))
+                'primary',
+                'secondary',
+                'tertiary'][display_type]))
 
     return all_featured_media
 
@@ -123,24 +123,24 @@ def create_featured_media_textbox():
 
     primaries = FeaturedMedia.query.order_by(
         FeaturedMedia.order.asc()).filter(
-        FeaturedMedia.display_type == u'primary').all()
+        FeaturedMedia.display_type == 'primary').all()
     secondaries = FeaturedMedia.query.order_by(
         FeaturedMedia.order.asc()).filter(
-        FeaturedMedia.display_type == u'secondary').all()
+        FeaturedMedia.display_type == 'secondary').all()
     tertiaries = FeaturedMedia.query.order_by(
         FeaturedMedia.order.asc()).filter(
-        FeaturedMedia.display_type == u'tertiary').all()
-    output_text = u''
+        FeaturedMedia.display_type == 'tertiary').all()
+    output_text = ''
     for display_type, feature_list in [
-            (_(u'Primary'),primaries),
-            (_(u'Secondary'),secondaries),
-            (_(u'Tertiary'),tertiaries)]:
+            (_('Primary'),primaries),
+            (_('Secondary'),secondaries),
+            (_('Tertiary'),tertiaries)]:
         output_text += _(
-            u"""-----------{display_type}-Features---------------------------
+            """-----------{display_type}-Features---------------------------
 """).format(display_type=display_type)
         for feature in feature_list:
             media_entry = feature.media_entry
-            output_text += u'/u/{uploader_username}/m/{media_slug}/\n'.format(
+            output_text += '/u/{uploader_username}/m/{media_slug}/\n'.format(
                 uploader_username = media_entry.get_actor.username,
                 media_slug = media_entry.slug)
 
@@ -164,9 +164,9 @@ def automatically_add_new_feature(media_entry):
     # secondary features, but in the future this should be a variable editable
     # by the site admin.
     too_many_primaries = FeaturedMedia.query.filter(
-        FeaturedMedia.display_type==u'primary').count() >= 1
+        FeaturedMedia.display_type=='primary').count() >= 1
     too_many_secondaries = FeaturedMedia.query.filter(
-        FeaturedMedia.display_type==u'secondary').count() >= 2
+        FeaturedMedia.display_type=='secondary').count() >= 2
     featured_first_to_last = FeaturedMedia.query.order_by(
         FeaturedMedia.order.asc()).all()
 
@@ -174,11 +174,11 @@ def automatically_add_new_feature(media_entry):
         # Some features have the option to demote or promote themselves to a
         # different display_type, based on their position. But all features move
         # up and down one step in the stack.
-        if (feature.is_last_of_type() and feature.display_type == u'primary'
+        if (feature.is_last_of_type() and feature.display_type == 'primary'
                 and too_many_primaries):
             feature.demote()
             too_many_primaries = False
-        elif (feature.is_last_of_type() and feature.display_type == u'secondary'
+        elif (feature.is_last_of_type() and feature.display_type == 'secondary'
                 and too_many_secondaries):
             feature.demote()
             too_many_secondaries = False
@@ -188,7 +188,7 @@ def automatically_add_new_feature(media_entry):
     # Create the new feature at the top of the stack.
     new_feature = FeaturedMedia(
         media_entry=media_entry,
-        display_type=u"primary",
+        display_type="primary",
         order=0)
     new_feature.save()
     return new_feature
@@ -252,10 +252,10 @@ def promote_feature(media_entry):
                                         target_feature.display_type)
         above_feature.save()
     # Change the feature's display type to a more prominent one
-    elif target_feature.display_type == u'secondary':
-        target_feature.display_type = u'primary'
-    elif target_feature.display_type == u'tertiary':
-        target_feature.display_type = u'secondary'
+    elif target_feature.display_type == 'secondary':
+        target_feature.display_type = 'primary'
+    elif target_feature.display_type == 'tertiary':
+        target_feature.display_type = 'secondary'
     target_feature.save()
 
 def demote_feature(media_entry):
@@ -287,8 +287,8 @@ def demote_feature(media_entry):
                                         target_feature.display_type)
         below_feature.save()
     # Change the feature's display type to a less prominent one
-    elif target_feature.display_type == u'secondary':
-        target_feature.display_type = u'tertiary'
-    elif target_feature.display_type == u'primary':
-        target_feature.display_type = u'secondary'
+    elif target_feature.display_type == 'secondary':
+        target_feature.display_type = 'tertiary'
+    elif target_feature.display_type == 'primary':
+        target_feature.display_type = 'secondary'
     target_feature.save()
diff --git a/mediagoblin/plugins/archivalook/views.py b/mediagoblin/plugins/archivalook/views.py
index 72424cfc..ca832a5c 100644
--- a/mediagoblin/plugins/archivalook/views.py
+++ b/mediagoblin/plugins/archivalook/views.py
@@ -40,15 +40,15 @@ def root_view(request):
     displaying featured media.
     """
     featured_media = {
-        u'primary':FeaturedMedia.query.order_by(
+        'primary':FeaturedMedia.query.order_by(
             FeaturedMedia.order.asc()).filter(
-            FeaturedMedia.display_type==u'primary').all(),
-        u'secondary':FeaturedMedia.query.order_by(
+            FeaturedMedia.display_type=='primary').all(),
+        'secondary':FeaturedMedia.query.order_by(
             FeaturedMedia.order.asc()).filter(
-            FeaturedMedia.display_type==u'secondary').all(),
-        u'tertiary':FeaturedMedia.query.order_by(
+            FeaturedMedia.display_type=='secondary').all(),
+        'tertiary':FeaturedMedia.query.order_by(
             FeaturedMedia.order.asc()).filter(
-            FeaturedMedia.display_type==u'tertiary').all()}
+            FeaturedMedia.display_type=='tertiary').all()}
 
     return render_to_response(
         request, 'archivalook/root.html',
@@ -56,7 +56,7 @@ def root_view(request):
         'allow_registration': mg_globals.app_config["allow_registration"],
         'feature_template': feature_template})
 
-@user_has_privilege(u'featurer')
+@user_has_privilege('featurer')
 def featured_media_panel(request):
     """
     This is a new administrator panel to manage featured media. This is an
@@ -99,7 +99,7 @@ def recent_media_gallery_view(request, page):
     """
     The replaced homepage is available through this view.
     """
-    cursor = MediaEntry.query.filter_by(state=u'processed').\
+    cursor = MediaEntry.query.filter_by(state='processed').\
         order_by(MediaEntry.created.desc())
 
     pagination = Pagination(page, cursor)
@@ -117,7 +117,7 @@ def add_featured_media_to_media_home(context):
     context['featured_media'] = FeaturedMedia.query
     return context
 
-@user_has_privilege(u'featurer')
+@user_has_privilege('featurer')
 @get_user_media_entry
 def feature_media(request, media, **kwargs):
     """
@@ -130,7 +130,7 @@ def feature_media(request, media, **kwargs):
     return redirect(
         request, 'index')
 
-@user_has_privilege(u'featurer')
+@user_has_privilege('featurer')
 @get_user_media_entry
 def unfeature_media(request, media, **kwargs):
     """
@@ -143,7 +143,7 @@ def unfeature_media(request, media, **kwargs):
     return redirect(
         request, 'index')
 
-@user_has_privilege(u'featurer')
+@user_has_privilege('featurer')
 @get_user_media_entry
 def promote_featured_media(request, media, **kwargs):
     """
@@ -156,7 +156,7 @@ def promote_featured_media(request, media, **kwargs):
     return redirect(
         request, 'index')
 
-@user_has_privilege(u'featurer')
+@user_has_privilege('featurer')
 @get_user_media_entry
 def demote_featured_media(request, media, **kwargs):
     """
diff --git a/mediagoblin/plugins/basic_auth/tools.py b/mediagoblin/plugins/basic_auth/tools.py
index 13f240b2..ad3cb1b0 100644
--- a/mediagoblin/plugins/basic_auth/tools.py
+++ b/mediagoblin/plugins/basic_auth/tools.py
@@ -40,7 +40,7 @@ def bcrypt_check_password(raw_pass, stored_hash, extra_salt=None):
       True or False depending on success.
     """
     if extra_salt:
-        raw_pass = u"%s:%s" % (extra_salt, raw_pass)
+        raw_pass = "{}:{}".format(extra_salt, raw_pass)
 
     hashed_pass = bcrypt.hashpw(raw_pass.encode('utf-8'), stored_hash)
 
@@ -66,9 +66,9 @@ def bcrypt_gen_password_hash(raw_pass, extra_salt=None):
       non-database extra salt
     """
     if extra_salt:
-        raw_pass = u"%s:%s" % (extra_salt, raw_pass)
+        raw_pass = "{}:{}".format(extra_salt, raw_pass)
 
-    return six.text_type(
+    return str(
         bcrypt.hashpw(raw_pass.encode('utf-8'), bcrypt.gensalt()))
 
 
@@ -92,8 +92,8 @@ def fake_login_attempt():
 
 
 EMAIL_FP_VERIFICATION_TEMPLATE = (
-    u"{uri}?"
-    u"token={fp_verification_key}")
+    "{uri}?"
+    "token={fp_verification_key}")
 
 
 def send_fp_verification_email(user, request):
diff --git a/mediagoblin/plugins/basic_auth/views.py b/mediagoblin/plugins/basic_auth/views.py
index f398f0d2..9433b22d 100644
--- a/mediagoblin/plugins/basic_auth/views.py
+++ b/mediagoblin/plugins/basic_auth/views.py
@@ -70,7 +70,7 @@ def forgot_password(request):
         success_message = _("An email has been sent with instructions "
                             "on how to change your password.")
 
-    if user and user.has_privilege(u'active') is False:
+    if user and user.has_privilege('active') is False:
         # Don't send reminder because user is inactive or has no verified email
         messages.add_message(
             request,
@@ -128,7 +128,7 @@ def verify_forgot_password(request):
             request, 'index')
 
     # check if user active and has email verified
-    if user.has_privilege(u'active'):
+    if user.has_privilege('active'):
         cp_form = forms.ChangeForgotPassForm(formdata_vars)
 
         if request.method == 'POST' and cp_form.validate():
diff --git a/mediagoblin/plugins/flatpagesfile/__init__.py b/mediagoblin/plugins/flatpagesfile/__init__.py
index 3d797809..5f267f07 100644
--- a/mediagoblin/plugins/flatpagesfile/__init__.py
+++ b/mediagoblin/plugins/flatpagesfile/__init__.py
@@ -34,7 +34,7 @@ _log = logging.getLogger(__name__)
 def print_context(c):
     s = []
     for key, val in c.items():
-        s.append('%s: %s' % (key, repr(val)))
+        s.append('{}: {}'.format(key, repr(val)))
     return '\n'.join(s)
 
 
diff --git a/mediagoblin/plugins/httpapiauth/__init__.py b/mediagoblin/plugins/httpapiauth/__init__.py
index d7180463..505cea5d 100644
--- a/mediagoblin/plugins/httpapiauth/__init__.py
+++ b/mediagoblin/plugins/httpapiauth/__init__.py
@@ -42,7 +42,7 @@ class HTTPAuth(Auth):
         if not request.authorization:
             return False
 
-        user = check_login_simple(six.text_type(request.authorization['username']),
+        user = check_login_simple(str(request.authorization['username']),
                                   request.authorization['password'])
 
         if user:
diff --git a/mediagoblin/plugins/ldap/tools.py b/mediagoblin/plugins/ldap/tools.py
index 9d6d8b2a..89ac8c11 100644
--- a/mediagoblin/plugins/ldap/tools.py
+++ b/mediagoblin/plugins/ldap/tools.py
@@ -23,12 +23,12 @@ from mediagoblin.tools import pluginapi
 _log = logging.getLogger(__name__)
 
 
-class LDAP(object):
+class LDAP:
     def __init__(self):
         self.ldap_settings = pluginapi.get_config('mediagoblin.plugins.ldap')
 
     def _connect(self, server):
-        _log.info('Connecting to {0}.'.format(server['LDAP_SERVER_URI']))
+        _log.info('Connecting to {}.'.format(server['LDAP_SERVER_URI']))
         self.conn = ldap.initialize(server['LDAP_SERVER_URI'])
 
         if server['LDAP_START_TLS'] == 'true':
@@ -38,7 +38,7 @@ class LDAP(object):
     def _get_email(self, server, username):
         try:
             results = self.conn.search_s(server['LDAP_SEARCH_BASE'],
-                                        ldap.SCOPE_SUBTREE, 'uid={0}'
+                                        ldap.SCOPE_SUBTREE, 'uid={}'
                                         .format(username),
                                         [server['EMAIL_SEARCH_FIELD']])
 
@@ -49,7 +49,7 @@ class LDAP(object):
         return email
 
     def login(self, username, password):
-        for k, v in six.iteritems(self.ldap_settings):
+        for k, v in self.ldap_settings.items():
             try:
                 self._connect(v)
                 user_dn = v['LDAP_USER_DN_TEMPLATE'].format(username=username)
@@ -61,7 +61,7 @@ class LDAP(object):
                 _log.info(e)
 
             finally:
-                _log.info('Unbinding {0}.'.format(v['LDAP_SERVER_URI']))
+                _log.info('Unbinding {}.'.format(v['LDAP_SERVER_URI']))
                 self.conn.unbind()
 
         return False, None
diff --git a/mediagoblin/plugins/ldap/views.py b/mediagoblin/plugins/ldap/views.py
index e10c7f60..99ecb456 100644
--- a/mediagoblin/plugins/ldap/views.py
+++ b/mediagoblin/plugins/ldap/views.py
@@ -44,7 +44,7 @@ def login(request):
 
             if user:
                 # set up login in session
-                request.session['user_id'] = six.text_type(user.id)
+                request.session['user_id'] = str(user.id)
                 request.session.save()
 
                 if request.form.get('next'):
diff --git a/mediagoblin/plugins/metadata_display/lib.py b/mediagoblin/plugins/metadata_display/lib.py
index c00bb0f6..5590b1fd 100644
--- a/mediagoblin/plugins/metadata_display/lib.py
+++ b/mediagoblin/plugins/metadata_display/lib.py
@@ -19,11 +19,11 @@ def rdfa_to_readable(rdfa_predicate):
     A simple script to convert rdfa resource descriptors into a form more
     accessible for humans.
     """
-    components = rdfa_predicate.split(u":")
+    components = rdfa_predicate.split(":")
     if len(components) >= 2:
         readable = components[1].capitalize()
     else:
-        readable = u""
+        readable = ""
     return readable
 
 def add_rdfa_to_readable_to_media_home(context):
diff --git a/mediagoblin/plugins/openid/models.py b/mediagoblin/plugins/openid/models.py
index 6773f0ad..e51b401c 100644
--- a/mediagoblin/plugins/openid/models.py
+++ b/mediagoblin/plugins/openid/models.py
@@ -41,7 +41,7 @@ class Nonce(Base):
     salt = Column(Unicode, primary_key=True)
 
     def __unicode__(self):
-        return u'Nonce: %r, %r' % (self.server_url, self.salt)
+        return 'Nonce: {!r}, {!r}'.format(self.server_url, self.salt)
 
 
 class Association(Base):
@@ -55,7 +55,7 @@ class Association(Base):
     assoc_type = Column(Unicode)
 
     def __unicode__(self):
-        return u'Association: %r, %r' % (self.server_url, self.handle)
+        return 'Association: {!r}, {!r}'.format(self.server_url, self.handle)
 
 
 MODELS = [
diff --git a/mediagoblin/plugins/openid/store.py b/mediagoblin/plugins/openid/store.py
index 24726814..dd794786 100644
--- a/mediagoblin/plugins/openid/store.py
+++ b/mediagoblin/plugins/openid/store.py
@@ -36,12 +36,12 @@ class SQLAlchemyOpenIDStore(OpenIDStore):
 
         if not assoc:
             assoc = Association()
-            assoc.server_url = six.text_type(server_url)
+            assoc.server_url = str(server_url)
             assoc.handle = association.handle
 
         # django uses base64 encoding, python-openid uses a blob field for
         # secret
-        assoc.secret = six.text_type(base64.encodestring(association.secret))
+        assoc.secret = str(base64.encodestring(association.secret))
         assoc.issued = association.issued
         assoc.lifetime = association.lifetime
         assoc.assoc_type = association.assoc_type
diff --git a/mediagoblin/plugins/openid/views.py b/mediagoblin/plugins/openid/views.py
index 71f444fa..03dee7d4 100644
--- a/mediagoblin/plugins/openid/views.py
+++ b/mediagoblin/plugins/openid/views.py
@@ -189,7 +189,7 @@ def finish_login(request):
 
     if user:
         # Set up login in session
-        request.session['user_id'] = six.text_type(user.id)
+        request.session['user_id'] = str(user.id)
         request.session.save()
 
         if request.session.get('next'):
diff --git a/mediagoblin/plugins/persona/views.py b/mediagoblin/plugins/persona/views.py
index 41d38353..3f1f1afb 100644
--- a/mediagoblin/plugins/persona/views.py
+++ b/mediagoblin/plugins/persona/views.py
@@ -65,7 +65,7 @@ def login(request):
         user = query.user if query else None
 
         if user:
-            request.session['user_id'] = six.text_type(user.id)
+            request.session['user_id'] = str(user.id)
             request.session['persona_login_email'] = email
             request.session.save()
 
diff --git a/mediagoblin/plugins/piwigo/tools.py b/mediagoblin/plugins/piwigo/tools.py
index 7b9b7af3..7b3e931a 100644
--- a/mediagoblin/plugins/piwigo/tools.py
+++ b/mediagoblin/plugins/piwigo/tools.py
@@ -47,9 +47,9 @@ class PwgNamedArray(list):
 
 
 def _fill_element_dict(el, data, as_attr=()):
-    for k, v in six.iteritems(data):
+    for k, v in data.items():
         if k in as_attr:
-            if not isinstance(v, six.string_types):
+            if not isinstance(v, str):
                 v = str(v)
             el.set(k, v)
         else:
@@ -63,7 +63,7 @@ def _fill_element(el, data):
             el.text = "1"
         else:
             el.text = "0"
-    elif isinstance(data, six.string_types):
+    elif isinstance(data, str):
         el.text = data
     elif isinstance(data, int):
         el.text = str(data)
@@ -92,7 +92,7 @@ def response_xml(result):
                     mimetype='text/xml', status=status)
 
 
-class CmdTable(object):
+class CmdTable:
     _cmd_table = {}
 
     def __init__(self, cmd_name, only_post=False):
@@ -131,11 +131,11 @@ def check_form(form):
         raise BadRequest()
     dump = []
     for f in form:
-        dump.append("%s=%r" % (f.name, f.data))
+        dump.append("{}={!r}".format(f.name, f.data))
     _log.debug("form: %s", " ".join(dump))
 
 
-class PWGSession(object):
+class PWGSession:
     session_manager = None
 
     def __init__(self, request):
diff --git a/mediagoblin/plugins/piwigo/views.py b/mediagoblin/plugins/piwigo/views.py
index 30c7ffa2..ecc7054e 100644
--- a/mediagoblin/plugins/piwigo/views.py
+++ b/mediagoblin/plugins/piwigo/views.py
@@ -121,7 +121,7 @@ def pwg_images_addSimple(request):
         raise BadRequest()
     dump = []
     for f in form:
-        dump.append("%s=%r" % (f.name, f.data))
+        dump.append("{}={!r}".format(f.name, f.data))
     _log.info("addSimple: %r %s %r", request.form, " ".join(dump),
               request.files)
 
@@ -133,8 +133,8 @@ def pwg_images_addSimple(request):
             mg_app=request.app, user=request.user,
             submitted_file=request.files['image'],
             filename=request.files['image'].filename,
-            title=six.text_type(form.name.data),
-            description=six.text_type(form.comment.data))
+            title=str(form.name.data),
+            description=str(form.comment.data))
 
         collection_id = form.category.data
         if collection_id > 0:
@@ -151,7 +151,7 @@ def pwg_images_addSimple(request):
     # Handle upload limit issues
     except FileUploadLimit:
         raise BadRequest(
-            _(u'Sorry, the file size is too big.'))
+            _('Sorry, the file size is too big.'))
     except UserUploadLimit:
         raise BadRequest(
             _('Sorry, uploading this file will put you over your'
diff --git a/mediagoblin/plugins/processing_info/__init__.py b/mediagoblin/plugins/processing_info/__init__.py
index fb824765..db6eeb0c 100644
--- a/mediagoblin/plugins/processing_info/__init__.py
+++ b/mediagoblin/plugins/processing_info/__init__.py
@@ -37,10 +37,10 @@ def make_stats(context):
     user = request.user
     if user:
         num_queued = MediaEntry.query.filter_by(
-                actor=user.id, state=u'processing').count()
+                actor=user.id, state='processing').count()
         context['num_queued'] = num_queued
         num_failed = MediaEntry.query.filter_by(
-                actor=user.id, state=u'failed').count()
+                actor=user.id, state='failed').count()
         context['num_failed'] = num_failed
     return context
 
diff --git a/mediagoblin/plugins/raven/__init__.py b/mediagoblin/plugins/raven/__init__.py
index 8cfaed0a..439d34c7 100644
--- a/mediagoblin/plugins/raven/__init__.py
+++ b/mediagoblin/plugins/raven/__init__.py
@@ -31,11 +31,11 @@ def get_client():
     client = None
 
     if sentry_dsn:
-        _log.info('Setting up raven from plugin config: {0}'.format(
+        _log.info('Setting up raven from plugin config: {}'.format(
             sentry_dsn))
         client = Client(sentry_dsn)
     elif os.environ.get('SENTRY_DSN'):
-        _log.info('Setting up raven from SENTRY_DSN environment variable: {0}'\
+        _log.info('Setting up raven from SENTRY_DSN environment variable: {}'\
                   .format(os.environ.get('SENTRY_DSN')))
         client = Client()  # Implicitly looks for SENTRY_DSN
 
diff --git a/mediagoblin/plugins/subtitles/views.py b/mediagoblin/plugins/subtitles/views.py
index e3375306..7ca45329 100644
--- a/mediagoblin/plugins/subtitles/views.py
+++ b/mediagoblin/plugins/subtitles/views.py
@@ -57,7 +57,7 @@ def edit_subtitles(request, media):
         if mimetypes.guess_type(
                 request.files['subtitle_file'].filename)[0] in \
                 UNSAFE_MIMETYPES:
-            public_filename = secure_filename('{0}.notsafe'.format(
+            public_filename = secure_filename('{}.notsafe'.format(
                 request.files['subtitle_file'].filename))
         else:
             public_filename = secure_filename(
@@ -72,7 +72,7 @@ def edit_subtitles(request, media):
             return redirect(request,
                             location=media.url_for_self(request.urlgen))
         subtitle_public_filepath = mg_globals.public_store.get_unique_filepath(
-            ['media_entries', six.text_type(media.id), 'subtitle',
+            ['media_entries', str(media.id), 'subtitle',
              public_filename])
 
         with mg_globals.public_store.get_file(
diff --git a/mediagoblin/plugins/trim_whitespace/__init__.py b/mediagoblin/plugins/trim_whitespace/__init__.py
index 3da1e8b4..6f67218c 100644
--- a/mediagoblin/plugins/trim_whitespace/__init__.py
+++ b/mediagoblin/plugins/trim_whitespace/__init__.py
@@ -13,7 +13,6 @@
 #
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see .
-from __future__ import unicode_literals
 import logging
 import re
 
@@ -23,7 +22,7 @@ _log = logging.getLogger(__name__)
 
 class TrimWhiteSpaceMeddleware(meddleware.BaseMeddleware):
     _setup_plugin_called = 0
-    RE_MULTI_WHITESPACE = re.compile(b'(\s)\s+', re.M)
+    RE_MULTI_WHITESPACE = re.compile(br'(\s)\s+', re.M)
 
     def process_response(self, request, response):
         """Perform very naive html tidying by removing multiple whitespaces"""
@@ -65,7 +64,7 @@ class TrimWhiteSpaceMeddleware(meddleware.BaseMeddleware):
 
         # Append ourselves to the list of enabled Meddlewares
         meddleware.ENABLED_MEDDLEWARE.append(
-            '{0}:{1}'.format(cls.__module__, cls.__name__))
+            '{}:{}'.format(cls.__module__, cls.__name__))
 
 
 hooks = {
diff --git a/mediagoblin/processing/__init__.py b/mediagoblin/processing/__init__.py
index 2897b5e7..f9289d14 100644
--- a/mediagoblin/processing/__init__.py
+++ b/mediagoblin/processing/__init__.py
@@ -35,7 +35,7 @@ from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
 _log = logging.getLogger(__name__)
 
 
-class ProgressCallback(object):
+class ProgressCallback:
     def __init__(self, entry):
         self.entry = entry
 
@@ -53,11 +53,11 @@ class ProgressCallback(object):
 def create_pub_filepath(entry, filename):
     return mgg.public_store.get_unique_filepath(
             ['media_entries',
-             six.text_type(entry.id),
+             str(entry.id),
              filename])
 
 
-class FilenameBuilder(object):
+class FilenameBuilder:
     """Easily slice and dice filenames.
 
     Initialize this class with an original file path, then use the fill()
@@ -90,7 +90,7 @@ class FilenameBuilder(object):
 
 
 
-class MediaProcessor(object):
+class MediaProcessor:
     """A particular processor for this media type.
 
     While the ProcessingManager handles all types of MediaProcessing
@@ -192,7 +192,7 @@ class ProcessingManagerDoesNotExist(ProcessingKeyError): pass
 
 
 
-class ProcessingManager(object):
+class ProcessingManager:
     """Manages all the processing actions available for a media type
 
     Specific processing actions, MediaProcessor subclasses, are added
@@ -290,7 +290,7 @@ def get_processing_manager_for_type(media_type):
     manager_class = hook_handle(('reprocess_manager', media_type))
     if not manager_class:
         raise ProcessingManagerDoesNotExist(
-            "A processing manager does not exist for {0}".format(media_type))
+            "A processing manager does not exist for {}".format(media_type))
     manager = manager_class()
 
     return manager
@@ -331,19 +331,19 @@ def mark_entry_failed(entry_id, exc):
         # metadata the user might have supplied.
         atomic_update(mgg.database.MediaEntry,
             {'id': entry_id},
-            {u'state': u'failed',
-             u'fail_error': six.text_type(exc.exception_path),
-             u'fail_metadata': exc.metadata})
+            {'state': 'failed',
+             'fail_error': str(exc.exception_path),
+             'fail_metadata': exc.metadata})
     else:
         _log.warn("No idea what happened here, but it failed: %r", exc)
         # Looks like no, let's record it so that admin could ask us about the
         # reason
         atomic_update(mgg.database.MediaEntry,
             {'id': entry_id},
-            {u'state': u'failed',
-             u'fail_error': u'Unhandled exception: {0}'.format(
-                 six.text_type(exc)),
-             u'fail_metadata': {}})
+            {'state': 'failed',
+             'fail_error': 'Unhandled exception: {}'.format(
+                 str(exc)),
+             'fail_metadata': {}})
 
 
 def get_process_filename(entry, workbench, acceptable_files):
@@ -391,7 +391,7 @@ def store_public(entry, keyname, local_file, target_name=None,
     try:
         mgg.public_store.copy_local_to_storage(local_file, target_filepath)
     except Exception as e:
-        _log.error(u'Exception happened: {0}'.format(e))
+        _log.error('Exception happened: {}'.format(e))
         raise PublicStoreFail(keyname=keyname)
     # raise an error if the file failed to copy
     if not mgg.public_store.file_exists(target_filepath):
@@ -400,7 +400,7 @@ def store_public(entry, keyname, local_file, target_name=None,
     entry.media_files[keyname] = target_filepath
 
 
-def copy_original(entry, orig_filename, target_name, keyname=u"original"):
+def copy_original(entry, orig_filename, target_name, keyname="original"):
     store_public(entry, keyname, orig_filename, target_name)
 
 
@@ -413,16 +413,16 @@ class BaseProcessingFail(Exception):
     and provide the exception_path and general_message applicable to
     this error.
     """
-    general_message = u''
+    general_message = ''
 
     @property
     def exception_path(self):
-        return u"%s:%s" % (
+        return "{}:{}".format(
             self.__class__.__module__, self.__class__.__name__)
 
     def __init__(self, message=None, **metadata):
         if message is not None:
-            super(BaseProcessingFail, self).__init__(message)
+            super().__init__(message)
             metadata['message'] = message
         self.metadata = metadata
 
@@ -431,7 +431,7 @@ class BadMediaFail(BaseProcessingFail):
     Error that should be raised when an inappropriate file was given
     for the media type specified.
     """
-    general_message = _(u'Invalid file given for media type.')
+    general_message = _('Invalid file given for media type.')
 
 
 class PublicStoreFail(BaseProcessingFail):
@@ -446,4 +446,4 @@ class ProcessFileNotFound(BaseProcessingFail):
     Error that should be raised when an acceptable file for processing
     is not found.
     """
-    general_message = _(u'An acceptable processing file was not found')
+    general_message = _('An acceptable processing file was not found')
diff --git a/mediagoblin/processing/task.py b/mediagoblin/processing/task.py
index bedfd32d..c62293e8 100644
--- a/mediagoblin/processing/task.py
+++ b/mediagoblin/processing/task.py
@@ -38,7 +38,7 @@ def handle_push_urls(feed_url):
     Retry 3 times every 2 minutes if run in separate process before failing."""
     if not mgg.app_config["push_urls"]:
         return # Nothing to do
-    _log.debug('Notifying Push servers for feed {0}'.format(feed_url))
+    _log.debug('Notifying Push servers for feed {}'.format(feed_url))
     hubparameters = {
         'hub.mode': 'publish',
         'hub.url': feed_url}
@@ -57,7 +57,7 @@ def handle_push_urls(feed_url):
                 return handle_push_urls.retry(exc=exc, throw=False)
             except Exception as e:
                 # All retries failed, Failure is no tragedy here, probably.
-                _log.warn('Failed to notify PuSH server for feed {0}. '
+                _log.warn('Failed to notify PuSH server for feed {}. '
                           'Giving up.'.format(feed_url))
                 return False
 
@@ -95,18 +95,18 @@ class ProcessMedia(celery.Task):
             with processor_class(manager, entry) as processor:
                 # Initial state change has to be here because
                 # the entry.state gets recorded on processor_class init
-                entry.state = u'processing'
+                entry.state = 'processing'
                 entry.save()
 
-                _log.debug('Processing {0}'.format(entry))
+                _log.debug('Processing {}'.format(entry))
 
                 try:
                     processor.process(**reprocess_info)
                 except Exception as exc:
                     if processor.entry_orig_state == 'processed':
                         _log.error(
-                            'Entry {0} failed to process due to the following'
-                            ' error: {1}'.format(entry.id, exc))
+                            'Entry {} failed to process due to the following'
+                            ' error: {}'.format(entry.id, exc))
                         _log.info(
                             'Setting entry.state back to "processed"')
                         pass
@@ -115,7 +115,7 @@ class ProcessMedia(celery.Task):
 
             # We set the state to processed and save the entry here so there's
             # no need to save at the end of the processing stage, probably ;)
-            entry.state = u'processed'
+            entry.state = 'processed'
             entry.save()
 
             # Notify the PuSH servers as async task
@@ -130,7 +130,7 @@ class ProcessMedia(celery.Task):
 
         except ImportError as exc:
             _log.error(
-                'Entry {0} failed to process due to an import error: {1}'\
+                'Entry {} failed to process due to an import error: {}'\
                     .format(
                     entry.title,
                     exc))
@@ -140,7 +140,7 @@ class ProcessMedia(celery.Task):
 
         except Exception as exc:
             _log.error('An unhandled exception was raised while'
-                    + ' processing {0}'.format(
+                    + ' processing {}'.format(
                         entry))
 
             mark_entry_failed(entry.id, exc)
diff --git a/mediagoblin/storage/__init__.py b/mediagoblin/storage/__init__.py
index 14f13bd3..bd6bad02 100644
--- a/mediagoblin/storage/__init__.py
+++ b/mediagoblin/storage/__init__.py
@@ -14,7 +14,6 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see .
 
-from __future__ import absolute_import
 
 import shutil
 import uuid
@@ -50,7 +49,7 @@ class NotImplementedError(Error):
 # Storage interface & basic file implementation
 ###############################################
 
-class StorageInterface(object):
+class StorageInterface:
     """
     Interface for the storage API.
 
@@ -148,7 +147,7 @@ class StorageInterface(object):
         filepath = clean_listy_filepath(filepath)
 
         if self.file_exists(filepath):
-            return filepath[:-1] + ["%s-%s" % (uuid.uuid4(), filepath[-1])]
+            return filepath[:-1] + ["{}-{}".format(uuid.uuid4(), filepath[-1])]
         else:
             return filepath
 
@@ -224,10 +223,10 @@ def clean_listy_filepath(listy_filepath):
       A cleaned list of unicode objects.
     """
     cleaned_filepath = [
-        six.text_type(secure_filename(filepath))
+        str(secure_filename(filepath))
         for filepath in listy_filepath]
 
-    if u'' in cleaned_filepath:
+    if '' in cleaned_filepath:
         raise InvalidFilepath(
             "A filename component could not be resolved into a usable name.")
 
@@ -261,7 +260,7 @@ def storage_system_from_config(config_section):
     """
     # This construct is needed, because dict(config) does
     # not replace the variables in the config items.
-    config_params = dict(six.iteritems(config_section))
+    config_params = dict(config_section.items())
 
     if 'storage_class' in config_params:
         storage_class = config_params['storage_class']
diff --git a/mediagoblin/storage/cloudfiles.py b/mediagoblin/storage/cloudfiles.py
index 61665ea0..0785f8a9 100644
--- a/mediagoblin/storage/cloudfiles.py
+++ b/mediagoblin/storage/cloudfiles.py
@@ -20,7 +20,6 @@ python-cloudfiles one.
 
 http://docs.python.org/whatsnew/2.5.html#pep-328-absolute-and-relative-imports
 '''
-from __future__ import absolute_import
 
 from mediagoblin.storage import StorageInterface, clean_listy_filepath
 
@@ -58,7 +57,7 @@ class CloudFilesStorage(StorageInterface):
             servicenet=True if self.param_use_servicenet == 'true' or \
                 self.param_use_servicenet == True else False)
 
-        _log.debug('Connected to {0} (auth: {1})'.format(
+        _log.debug('Connected to {} (auth: {})'.format(
             self.connection.connection.host,
             self.connection.auth.host))
 
@@ -72,7 +71,7 @@ class CloudFilesStorage(StorageInterface):
             self.container = self.connection.get_container(
                 self.param_container)
 
-        _log.debug('Container: {0}'.format(
+        _log.debug('Container: {}'.format(
             self.container.name))
 
         self.container_uri = self.container.public_ssl_uri()
@@ -162,7 +161,7 @@ class CloudFilesStorage(StorageInterface):
         # and bandwidth usage. So, override this method and use the
         # Cloudfile's "send" interface instead.
         # TODO: Fixing write() still seems worthwhile though.
-        _log.debug('Sending {0} to cloudfiles...'.format(filepath))
+        _log.debug('Sending {} to cloudfiles...'.format(filepath))
         with self.get_file(filepath, 'wb') as dest_file:
             with open(filename, 'rb') as source_file:
                 # Copy to storage system in 4096 byte chunks
@@ -188,7 +187,7 @@ class CloudFilesStorageObjectWrapper():
         self.storage_object = storage_object
 
     def read(self, *args, **kwargs):
-        _log.debug('Reading {0}'.format(
+        _log.debug('Reading {}'.format(
             self.storage_object.name))
         return self.storage_object.read(*args, **kwargs)
 
diff --git a/mediagoblin/storage/filestorage.py b/mediagoblin/storage/filestorage.py
index 89f43276..fe0d3c11 100644
--- a/mediagoblin/storage/filestorage.py
+++ b/mediagoblin/storage/filestorage.py
@@ -32,7 +32,7 @@ class FileObjectAwareFile(io.FileIO):
             # object, which should be saved RAM-friendly way
             shutil.copyfileobj(data, self)
         else:
-            super(FileObjectAwareFile, self).write(data)
+            super().write(data)
 
 
 class BasicFileStorage(StorageInterface):
diff --git a/mediagoblin/storage/mountstorage.py b/mediagoblin/storage/mountstorage.py
index a829db31..66ae7c3c 100644
--- a/mediagoblin/storage/mountstorage.py
+++ b/mediagoblin/storage/mountstorage.py
@@ -14,7 +14,6 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see .
 
-from __future__ import print_function
 
 import six
 
@@ -124,7 +123,7 @@ class MountStorage(StorageInterface):
         v = table.get(None)
         if v:
             res.append("  " * len(indent) + repr(indent) + ": " + repr(v))
-        for k, v in six.iteritems(table):
+        for k, v in table.items():
             if k == None:
                 continue
             res.append("  " * len(indent) + repr(k) + ":")
diff --git a/mediagoblin/submit/forms.py b/mediagoblin/submit/forms.py
index 69d211e6..ac605c73 100644
--- a/mediagoblin/submit/forms.py
+++ b/mediagoblin/submit/forms.py
@@ -27,7 +27,7 @@ def get_submit_start_form(form, **kwargs):
     max_file_size = kwargs.get('max_file_size')
     desc = None
     if max_file_size:
-        desc = _('Max file size: {0} mb'.format(max_file_size))
+        desc = _('Max file size: {} mb'.format(max_file_size))
 
     class SubmitStartForm(wtforms.Form):
         file = wtforms.FileField(
diff --git a/mediagoblin/submit/lib.py b/mediagoblin/submit/lib.py
index 9ec96923..ad08e14f 100644
--- a/mediagoblin/submit/lib.py
+++ b/mediagoblin/submit/lib.py
@@ -105,7 +105,7 @@ class UserPastUploadLimit(UploadLimitError):
 
 def submit_media(mg_app, user, submitted_file, filename,
                  title=None, description=None, collection_slug=None,
-                 license=None, metadata=None, tags_string=u"",
+                 license=None, metadata=None, tags_string="",
                  callback_url=None, urlgen=None,):
     """
     Args:
@@ -132,7 +132,7 @@ def submit_media(mg_app, user, submitted_file, filename,
 
     # If the filename contains non ascii generate a unique name
     if not all(ord(c) < 128 for c in filename):
-        filename = six.text_type(uuid.uuid4()) + splitext(filename)[-1]
+        filename = str(uuid.uuid4()) + splitext(filename)[-1]
 
     # Sniff the submitted media to determine which
     # media plugin should handle processing
@@ -141,9 +141,9 @@ def submit_media(mg_app, user, submitted_file, filename,
     # create entry and save in database
     entry = new_upload_entry(user)
     entry.media_type = media_type
-    entry.title = (title or six.text_type(splitext(filename)[0]))
+    entry.title = (title or str(splitext(filename)[0]))
 
-    entry.description = description or u""
+    entry.description = description or ""
 
     entry.license = license or None
 
@@ -163,7 +163,7 @@ def submit_media(mg_app, user, submitted_file, filename,
     # Get file size and round to 2 decimal places
     file_size = mg_app.queue_store.get_file_size(
         entry.queued_media_file) / (1024.0 * 1024)
-    file_size = float('{0:.2f}'.format(file_size))
+    file_size = float('{:.2f}'.format(file_size))
 
     # Check if file size is over the limit
     if max_file_size and file_size >= max_file_size:
@@ -233,7 +233,7 @@ def prepare_queue_task(app, entry, filename):
     # (If we got it off the task's auto-generation, there'd be
     # a risk of a race condition when we'd save after sending
     # off the task)
-    task_id = six.text_type(uuid.uuid4())
+    task_id = str(uuid.uuid4())
     entry.queued_task_id = task_id
 
     # Now store generate the queueing related filename
diff --git a/mediagoblin/submit/views.py b/mediagoblin/submit/views.py
index 7bbfb645..db7ad82a 100644
--- a/mediagoblin/submit/views.py
+++ b/mediagoblin/submit/views.py
@@ -37,7 +37,7 @@ from mediagoblin.user_pages.lib import add_media_to_collection
 
 
 @require_active_login
-@user_has_privilege(u'uploader')
+@user_has_privilege('uploader')
 def submit_start(request):
     """
     First view for submitting a file.
@@ -65,16 +65,16 @@ def submit_start(request):
     if request.method == 'POST' and submit_form.validate():
         if not check_file_field(request, 'file'):
             submit_form.file.errors.append(
-                _(u'You must provide a file.'))
+                _('You must provide a file.'))
         else:
             try:
                 media = submit_media(
                     mg_app=request.app, user=request.user,
                     submitted_file=request.files['file'],
                     filename=request.files['file'].filename,
-                    title=six.text_type(submit_form.title.data),
-                    description=six.text_type(submit_form.description.data),
-                    license=six.text_type(submit_form.license.data) or None,
+                    title=str(submit_form.title.data),
+                    description=str(submit_form.description.data),
+                    license=str(submit_form.license.data) or None,
                     tags_string=submit_form.tags.data,
                     urlgen=request.urlgen)
 
@@ -97,7 +97,7 @@ def submit_start(request):
             # Handle upload limit issues
             except FileUploadLimit:
                 submit_form.file.errors.append(
-                    _(u'Sorry, the file size is too big.'))
+                    _('Sorry, the file size is too big.'))
             except UserUploadLimit:
                 submit_form.file.errors.append(
                     _('Sorry, uploading this file will put you over your'
@@ -131,8 +131,8 @@ def add_collection(request, media=None):
     if request.method == 'POST' and submit_form.validate():
         collection = request.db.Collection()
 
-        collection.title = six.text_type(submit_form.title.data)
-        collection.description = six.text_type(submit_form.description.data)
+        collection.title = str(submit_form.title.data)
+        collection.description = str(submit_form.description.data)
         collection.actor = request.user.id
         collection.type = request.db.Collection.USER_DEFINED_TYPE
         collection.generate_slug()
diff --git a/mediagoblin/tests/test_api.py b/mediagoblin/tests/test_api.py
index f4741fd1..23548f23 100644
--- a/mediagoblin/tests/test_api.py
+++ b/mediagoblin/tests/test_api.py
@@ -16,7 +16,7 @@
 import json
 
 try:
-    import mock
+    from unittest import mock
 except ImportError:
     import unittest.mock as mock
 import pytest
@@ -30,7 +30,7 @@ from mediagoblin.tests.tools import fixture_add_user
 from mediagoblin.moderation.tools import take_away_privileges
 
 
-class TestAPI(object):
+class TestAPI:
     """ Test mediagoblin's pump.io complient APIs """
 
     @pytest.fixture(autouse=True)
@@ -38,11 +38,11 @@ class TestAPI(object):
         self.test_app = test_app
         self.db = mg_globals.database
 
-        self.user = fixture_add_user(privileges=[u'active', u'uploader',
-                                                 u'commenter'])
+        self.user = fixture_add_user(privileges=['active', 'uploader',
+                                                 'commenter'])
         self.other_user = fixture_add_user(
             username="otheruser",
-            privileges=[u'active', u'uploader', u'commenter']
+            privileges=['active', 'uploader', 'commenter']
         )
         self.active_user = self.user
 
@@ -55,7 +55,7 @@ class TestAPI(object):
 
         with self.mock_oauth():
             response = test_app.post(
-                "/api/user/{0}/feed".format(self.active_user.username),
+                "/api/user/{}/feed".format(self.active_user.username),
                 json.dumps(activity),
                 headers=headers
             )
@@ -75,7 +75,7 @@ class TestAPI(object):
 
         with self.mock_oauth():
             response = test_app.post(
-                "/api/user/{0}/uploads".format(self.active_user.username),
+                "/api/user/{}/uploads".format(self.active_user.username),
                 data,
                 headers=headers
             )
@@ -183,7 +183,7 @@ class TestAPI(object):
             # Will be self.user trying to upload as self.other_user
             with pytest.raises(AppError) as excinfo:
                 test_app.post(
-                    "/api/user/{0}/uploads".format(self.other_user.username),
+                    "/api/user/{}/uploads".format(self.other_user.username),
                     data,
                     headers=headers
                 )
@@ -206,7 +206,7 @@ class TestAPI(object):
         with self.mock_oauth():
             with pytest.raises(AppError) as excinfo:
                 test_app.post(
-                    "/api/user/{0}/feed".format(self.other_user.username),
+                    "/api/user/{}/feed".format(self.other_user.username),
                     json.dumps(activity),
                     headers=headers
                 )
@@ -241,7 +241,7 @@ class TestAPI(object):
         with self.mock_oauth():
             with pytest.raises(AppError) as excinfo:
                 test_app.post(
-                    "/api/user/{0}/feed".format(self.user.username),
+                    "/api/user/{}/feed".format(self.user.username),
                     json.dumps(activity),
                     headers=headers
                 )
@@ -268,7 +268,7 @@ class TestAPI(object):
 
         with self.mock_oauth():
             response = test_app.post(
-                "/api/user/{0}/feed".format(self.user.username),
+                "/api/user/{}/feed".format(self.user.username),
                 json.dumps(activity),
                 headers={"Content-Type": "application/json"}
             )
@@ -290,7 +290,7 @@ class TestAPI(object):
     def test_only_uploaders_post_image(self, test_app):
         """ Test that only uploaders can upload images """
         # Remove uploader permissions from user
-        take_away_privileges(self.user.username, u"uploader")
+        take_away_privileges(self.user.username, "uploader")
 
         # Now try and upload a image
         data = open(GOOD_JPG, "rb").read()
@@ -302,7 +302,7 @@ class TestAPI(object):
         with self.mock_oauth():
             with pytest.raises(AppError) as excinfo:
                 test_app.post(
-                    "/api/user/{0}/uploads".format(self.user.username),
+                    "/api/user/{}/uploads".format(self.user.username),
                     data,
                     headers=headers
                 )
@@ -397,7 +397,7 @@ class TestAPI(object):
         with self.mock_oauth():
             with pytest.raises(AppError) as excinfo:
                 test_app.post(
-                    "/api/user/{0}/feed".format(self.other_user.username),
+                    "/api/user/{}/feed".format(self.other_user.username),
                     json.dumps(activity),
                     headers=headers
                 )
@@ -443,7 +443,7 @@ class TestAPI(object):
         with self.mock_oauth():
             with pytest.raises(AppError) as excinfo:
                 test_app.post(
-                    "/api/user/{0}/feed".format(self.user.username),
+                    "/api/user/{}/feed".format(self.user.username),
                     json.dumps(activity),
                     headers=headers
                 )
@@ -452,7 +452,7 @@ class TestAPI(object):
 
     def test_profile(self, test_app):
         """ Tests profile endpoint """
-        uri = "/api/user/{0}/profile".format(self.user.username)
+        uri = "/api/user/{}/profile".format(self.user.username)
         with self.mock_oauth():
             response = test_app.get(uri)
             profile = json.loads(response.body.decode())
@@ -466,7 +466,7 @@ class TestAPI(object):
 
     def test_user(self, test_app):
         """ Test the user endpoint """
-        uri = "/api/user/{0}/".format(self.user.username)
+        uri = "/api/user/{}/".format(self.user.username)
         with self.mock_oauth():
             response = test_app.get(uri)
             user = json.loads(response.body.decode())
@@ -492,7 +492,7 @@ class TestAPI(object):
         response, image_data = self._upload_image(test_app, GOOD_JPG)
         response, data = self._post_image_to_feed(test_app, image_data)
 
-        uri = "/api/user/{0}/feed".format(self.active_user.username)
+        uri = "/api/user/{}/feed".format(self.active_user.username)
         with self.mock_oauth():
             response = test_app.get(uri)
             feed = json.loads(response.body.decode())
@@ -565,7 +565,7 @@ class TestAPI(object):
         self.active_user = self.other_user
 
         # Fetch the feed
-        url = "/api/user/{0}/feed".format(self.user.username)
+        url = "/api/user/{}/feed".format(self.user.username)
         with self.mock_oauth():
             response = test_app.get(url)
             feed = json.loads(response.body.decode())
diff --git a/mediagoblin/tests/test_auth.py b/mediagoblin/tests/test_auth.py
index 9cf5ccb0..dafaa1e7 100644
--- a/mediagoblin/tests/test_auth.py
+++ b/mediagoblin/tests/test_auth.py
@@ -47,9 +47,9 @@ def test_register_views(test_app):
         '/auth/register/', {})
     context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
     form = context['register_form']
-    assert form.username.errors == [u'This field is required.']
-    assert form.password.errors == [u'This field is required.']
-    assert form.email.errors == [u'This field is required.']
+    assert form.username.errors == ['This field is required.']
+    assert form.password.errors == ['This field is required.']
+    assert form.email.errors == ['This field is required.']
 
     # Try to register with fields that are known to be invalid
     # --------------------------------------------------------
@@ -64,8 +64,8 @@ def test_register_views(test_app):
     context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
     form = context['register_form']
 
-    assert form.username.errors == [u'Field must be between 3 and 30 characters long.']
-    assert form.password.errors == [u'Field must be between 5 and 1024 characters long.']
+    assert form.username.errors == ['Field must be between 3 and 30 characters long.']
+    assert form.password.errors == ['Field must be between 5 and 1024 characters long.']
 
     ## bad form
     template.clear_test_template_context()
@@ -76,8 +76,8 @@ def test_register_views(test_app):
     context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
     form = context['register_form']
 
-    assert form.username.errors == [u'This field does not take email addresses.']
-    assert form.email.errors == [u'This field requires an email address.']
+    assert form.username.errors == ['This field does not take email addresses.']
+    assert form.email.errors == ['This field requires an email address.']
 
     ## invalid characters
     template.clear_test_template_context()
@@ -88,7 +88,7 @@ def test_register_views(test_app):
     context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
     form = context['register_form']
 
-    assert form.username.errors == [u'Invalid input.']
+    assert form.username.errors == ['Invalid input.']
 
     ## At this point there should be no users in the database ;)
     assert User.query.count() == 0
@@ -109,7 +109,7 @@ def test_register_views(test_app):
     template.clear_test_template_context()
     response = test_app.post(
         '/auth/register/', {
-            'username': u'angrygirl',
+            'username': 'angrygirl',
             'password': 'iamsoangry',
             'email': 'angrygrrl@example.org'})
     response.follow()
@@ -120,20 +120,20 @@ def test_register_views(test_app):
 
     ## Make sure user is in place
     new_user = mg_globals.database.LocalUser.query.filter(
-        LocalUser.username==u'angrygirl'
+        LocalUser.username=='angrygirl'
     ).first()
     assert new_user
 
     ## Make sure that the proper privileges are granted on registration
 
-    assert new_user.has_privilege(u'commenter')
-    assert new_user.has_privilege(u'uploader')
-    assert new_user.has_privilege(u'reporter')
-    assert not new_user.has_privilege(u'active')
+    assert new_user.has_privilege('commenter')
+    assert new_user.has_privilege('uploader')
+    assert new_user.has_privilege('reporter')
+    assert not new_user.has_privilege('active')
     ## Make sure user is logged in
     request = template.TEMPLATE_TEST_CONTEXT[
         'mediagoblin/user_pages/user_nonactive.html']['request']
-    assert request.session['user_id'] == six.text_type(new_user.id)
+    assert request.session['user_id'] == str(new_user.id)
 
     ## Make sure we get email confirmation, and try verifying
     assert len(mail.EMAIL_TEST_INBOX) == 2
@@ -145,7 +145,7 @@ def test_register_views(test_app):
 
     path = urlparse.urlsplit(email_context['verification_url'])[2]
     get_params = urlparse.urlsplit(email_context['verification_url'])[3]
-    assert path == u'/auth/verify_email/'
+    assert path == '/auth/verify_email/'
     parsed_get_params = urlparse.parse_qs(get_params)
 
     ## Try verifying with bs verification key, shouldn't work
@@ -160,20 +160,20 @@ def test_register_views(test_app):
     # assert context['verification_successful'] == True
     # TODO: Would be good to test messages here when we can do so...
     new_user = mg_globals.database.LocalUser.query.filter(
-        LocalUser.username==u'angrygirl'
+        LocalUser.username=='angrygirl'
     ).first()
     assert new_user
 
     ## Verify the email activation works
     template.clear_test_template_context()
-    response = test_app.get("%s?%s" % (path, get_params))
+    response = test_app.get("{}?{}".format(path, get_params))
     response.follow()
     context = template.TEMPLATE_TEST_CONTEXT[
         'mediagoblin/user_pages/user.html']
     # assert context['verification_successful'] == True
     # TODO: Would be good to test messages here when we can do so...
     new_user = mg_globals.database.LocalUser.query.filter(
-        LocalUser.username==u'angrygirl'
+        LocalUser.username=='angrygirl'
     ).first()
     assert new_user
 
@@ -183,7 +183,7 @@ def test_register_views(test_app):
     template.clear_test_template_context()
     response = test_app.post(
         '/auth/register/', {
-            'username': u'angrygirl',
+            'username': 'angrygirl',
             'password': 'iamsoangry2',
             'email': 'angrygrrl2@example.org'})
 
@@ -191,7 +191,7 @@ def test_register_views(test_app):
         'mediagoblin/auth/register.html']
     form = context['register_form']
     assert form.username.errors == [
-        u'Sorry, a user with that name already exists.']
+        'Sorry, a user with that name already exists.']
 
     ## TODO: Also check for double instances of an email address?
 
@@ -200,7 +200,7 @@ def test_register_views(test_app):
     template.clear_test_template_context()
     response = test_app.post(
         '/auth/forgot_password/',
-        {'username': u'angrygirl'})
+        {'username': 'angrygirl'})
     response.follow()
 
     ## Did we redirect to the proper page?  Use the right template?
@@ -219,7 +219,7 @@ def test_register_views(test_app):
     path = urlparse.urlsplit(email_context['verification_url'])[2]
     get_params = urlparse.urlsplit(email_context['verification_url'])[3]
     parsed_get_params = urlparse.parse_qs(get_params)
-    assert path == u'/auth/forgot_password/verify/'
+    assert path == '/auth/forgot_password/verify/'
 
     ## Try using a bs password-changing verification key, shouldn't work
     template.clear_test_template_context()
@@ -232,7 +232,7 @@ def test_register_views(test_app):
 
     ## Verify step 1 of password-change works -- can see form to change password
     template.clear_test_template_context()
-    response = test_app.get("%s?%s" % (path, get_params))
+    response = test_app.get("{}?{}".format(path, get_params))
     assert 'mediagoblin/plugins/basic_auth/change_fp.html' in \
             template.TEMPLATE_TEST_CONTEXT
 
@@ -249,7 +249,7 @@ def test_register_views(test_app):
     template.clear_test_template_context()
     response = test_app.post(
         '/auth/login/', {
-            'username': u'angrygirl',
+            'username': 'angrygirl',
             'password': 'iamveryveryangry'})
 
     # User should be redirected
@@ -276,24 +276,24 @@ def test_authentication_views(test_app):
     response = test_app.post('/auth/login/')
     context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
     form = context['login_form']
-    assert form.username.errors == [u'This field is required.']
+    assert form.username.errors == ['This field is required.']
 
     # Failed login - blank user
     # -------------------------
     template.clear_test_template_context()
     response = test_app.post(
         '/auth/login/', {
-            'password': u'toast'})
+            'password': 'toast'})
     context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
     form = context['login_form']
-    assert form.username.errors == [u'This field is required.']
+    assert form.username.errors == ['This field is required.']
 
     # Failed login - blank password
     # -----------------------------
     template.clear_test_template_context()
     response = test_app.post(
         '/auth/login/', {
-            'username': u'chris'})
+            'username': 'chris'})
     assert 'mediagoblin/auth/login.html' in template.TEMPLATE_TEST_CONTEXT
 
     # Failed login - bad user
@@ -301,7 +301,7 @@ def test_authentication_views(test_app):
     template.clear_test_template_context()
     response = test_app.post(
         '/auth/login/', {
-            'username': u'steve',
+            'username': 'steve',
             'password': 'toast'})
     context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
     assert context['login_failed']
@@ -311,7 +311,7 @@ def test_authentication_views(test_app):
     template.clear_test_template_context()
     response = test_app.post(
         '/auth/login/', {
-            'username': u'chris',
+            'username': 'chris',
             'password': 'jam_and_ham'})
     context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
     assert context['login_failed']
@@ -321,7 +321,7 @@ def test_authentication_views(test_app):
     template.clear_test_template_context()
     response = test_app.post(
         '/auth/login/', {
-            'username': u'chris',
+            'username': 'chris',
             'password': 'toast'})
 
     # User should be redirected
@@ -332,7 +332,7 @@ def test_authentication_views(test_app):
     # Make sure user is in the session
     context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
     session = context['request'].session
-    assert session['user_id'] == six.text_type(test_user.id)
+    assert session['user_id'] == str(test_user.id)
 
     # Successful logout
     # -----------------
@@ -354,7 +354,7 @@ def test_authentication_views(test_app):
     template.clear_test_template_context()
     response = test_app.post(
         '/auth/login/', {
-            'username': u'chris',
+            'username': 'chris',
             'password': 'toast',
             'next' : '/u/chris/'})
     assert urlparse.urlsplit(response.location)[2] == '/u/chris/'
@@ -363,22 +363,22 @@ def test_authentication_views(test_app):
     template.clear_test_template_context()
     response = test_app.post(
         '/auth/login/', {
-            'username': u'ANDREW',
+            'username': 'ANDREW',
             'password': 'fuselage'})
     context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
     form = context['login_form']
 
     # Username should no longer be uppercased; it should be lowercased
-    assert not form.username.data == u'ANDREW'
-    assert form.username.data == u'andrew'
+    assert not form.username.data == 'ANDREW'
+    assert form.username.data == 'andrew'
 
     # Successful login with short user
     # --------------------------------
-    short_user = fixture_add_user(username=u'me', password=u'sho')
+    short_user = fixture_add_user(username='me', password='sho')
     template.clear_test_template_context()
     response = test_app.post(
         '/auth/login/', {
-            'username': u'me',
+            'username': 'me',
             'password': 'sho'})
 
     # User should be redirected
@@ -390,7 +390,7 @@ def test_authentication_views(test_app):
     # Make sure user is in the session
     context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
     session = context['request'].session
-    assert session['user_id'] == six.text_type(short_user.id)
+    assert session['user_id'] == str(short_user.id)
 
     # Must logout
     template.clear_test_template_context()
@@ -399,11 +399,11 @@ def test_authentication_views(test_app):
     # Successful login with long user
     # ----------------
     long_user = fixture_add_user(
-        username=u'realllylonguser@reallylongdomain.com.co', password=u'sho')
+        username='realllylonguser@reallylongdomain.com.co', password='sho')
     template.clear_test_template_context()
     response = test_app.post(
         '/auth/login/', {
-            'username': u'realllylonguser@reallylongdomain.com.co',
+            'username': 'realllylonguser@reallylongdomain.com.co',
             'password': 'sho'})
 
     # User should be redirected
@@ -414,7 +414,7 @@ def test_authentication_views(test_app):
     # Make sure user is in the session
     context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
     session = context['request'].session
-    assert session['user_id'] == six.text_type(long_user.id)
+    assert session['user_id'] == str(long_user.id)
 
     template.clear_test_template_context()
     response = test_app.get('/auth/logout/')
diff --git a/mediagoblin/tests/test_basic_auth.py b/mediagoblin/tests/test_basic_auth.py
index 3a42e407..f7553fe1 100644
--- a/mediagoblin/tests/test_basic_auth.py
+++ b/mediagoblin/tests/test_basic_auth.py
@@ -68,13 +68,13 @@ def test_bcrypt_gen_password_hash():
 def test_change_password(test_app):
         """Test changing password correctly and incorrectly"""
         test_user = fixture_add_user(
-            password=u'toast',
-            privileges=[u'active'])
+            password='toast',
+            privileges=['active'])
 
         test_app.post(
             '/auth/login/', {
-                'username': u'chris',
-                'password': u'toast'})
+                'username': 'chris',
+                'password': 'toast'})
 
         # test that the password can be changed
         res = test_app.post(
@@ -88,7 +88,7 @@ def test_change_password(test_app):
         assert urlparse.urlsplit(res.location)[2] == '/edit/account/'
 
         # test_user has to be fetched again in order to have the current values
-        test_user = LocalUser.query.filter(LocalUser.username==u'chris').first()
+        test_user = LocalUser.query.filter(LocalUser.username=='chris').first()
         assert auth_tools.bcrypt_check_password('123456', test_user.pw_hash)
 
         # test that the password cannot be changed if the given
@@ -100,5 +100,5 @@ def test_change_password(test_app):
                 'new_password': '098765',
                 })
 
-        test_user = LocalUser.query.filter(LocalUser.username==u'chris').first()
+        test_user = LocalUser.query.filter(LocalUser.username=='chris').first()
         assert not auth_tools.bcrypt_check_password('098765', test_user.pw_hash)
diff --git a/mediagoblin/tests/test_config.py b/mediagoblin/tests/test_config.py
index c3527418..bff2fc51 100644
--- a/mediagoblin/tests/test_config.py
+++ b/mediagoblin/tests/test_config.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 #
 # GNU MediaGoblin -- federated, autonomous media hosting
 # Copyright (C) 2011, 2012 MediaGoblin contributors.  See AUTHORS.
@@ -49,7 +48,7 @@ def test_read_mediagoblin_config():
     assert this_conf['carrotapp']['num_carrots'] == 88
     assert this_conf['carrotapp']['encouragement_phrase'] == \
         "I'd love it if you eat your carrots!"
-    assert this_conf['carrotapp']['blah_blah'] == u"blæh!"
+    assert this_conf['carrotapp']['blah_blah'] == "blæh!"
     assert this_conf['celery']['EAT_CELERY_WITH_CARROTS'] == False
 
     # A bad file
diff --git a/mediagoblin/tests/test_edit.py b/mediagoblin/tests/test_edit.py
index 5c109be6..c2cee048 100644
--- a/mediagoblin/tests/test_edit.py
+++ b/mediagoblin/tests/test_edit.py
@@ -25,12 +25,12 @@ from mediagoblin import auth
 from mediagoblin.tools import template, mail
 
 
-class TestUserEdit(object):
+class TestUserEdit:
     def setup(self):
         # set up new user
-        self.user_password = u'toast'
+        self.user_password = 'toast'
         self.user = fixture_add_user(password = self.user_password,
-                               privileges=[u'active'])
+                               privileges=['active'])
 
     def login(self, test_app):
         test_app.post(
@@ -44,19 +44,19 @@ class TestUserEdit(object):
         self.login(test_app)
 
         # Make sure user exists
-        assert LocalUser.query.filter(LocalUser.username==u'chris').first()
+        assert LocalUser.query.filter(LocalUser.username=='chris').first()
 
         res = test_app.post('/edit/account/delete/', {'confirmed': 'y'})
 
         # Make sure user has been deleted
-        assert LocalUser.query.filter(LocalUser.username==u'chris').first() == None
+        assert LocalUser.query.filter(LocalUser.username=='chris').first() == None
 
         #TODO: make sure all corresponding items comments etc have been
         # deleted too. Perhaps in submission test?
 
         #Restore user at end of test
         self.user = fixture_add_user(password = self.user_password,
-                               privileges=[u'active'])
+                               privileges=['active'])
         self.login(test_app)
 
 
@@ -67,8 +67,8 @@ class TestUserEdit(object):
         # Test if legacy profile editing URL redirects correctly
         res = test_app.post(
             '/edit/profile/', {
-                'bio': u'I love toast!',
-                'url': u'http://dustycloud.org/'}, expect_errors=True)
+                'bio': 'I love toast!',
+                'url': 'http://dustycloud.org/'}, expect_errors=True)
 
         # Should redirect to /u/chris/edit/
         assert res.status_int == 302
@@ -76,20 +76,20 @@ class TestUserEdit(object):
 
         res = test_app.post(
             '/u/chris/edit/', {
-                'bio': u'I love toast!',
-                'url': u'http://dustycloud.org/'})
+                'bio': 'I love toast!',
+                'url': 'http://dustycloud.org/'})
 
-        test_user = LocalUser.query.filter(LocalUser.username==u'chris').first()
-        assert test_user.bio == u'I love toast!'
-        assert test_user.url == u'http://dustycloud.org/'
+        test_user = LocalUser.query.filter(LocalUser.username=='chris').first()
+        assert test_user.bio == 'I love toast!'
+        assert test_user.url == 'http://dustycloud.org/'
 
         # change a different user than the logged in (should fail with 403)
-        fixture_add_user(username=u"foo",
-                         privileges=[u'active'])
+        fixture_add_user(username="foo",
+                         privileges=['active'])
         res = test_app.post(
             '/u/foo/edit/', {
-                'bio': u'I love toast!',
-                'url': u'http://dustycloud.org/'}, expect_errors=True)
+                'bio': 'I love toast!',
+                'url': 'http://dustycloud.org/'}, expect_errors=True)
         assert res.status_int == 403
 
         # test changing the bio and the URL inproperly
@@ -107,9 +107,9 @@ class TestUserEdit(object):
         form = context['form']
 
         assert form.bio.errors == [
-            u'Field must be between 0 and 500 characters long.']
+            'Field must be between 0 and 500 characters long.']
         assert form.url.errors == [
-            u'This address contains errors']
+            'This address contains errors']
 
     def test_email_change(self, test_app):
         self.login(test_app)
@@ -125,7 +125,7 @@ class TestUserEdit(object):
         context = template.TEMPLATE_TEST_CONTEXT[
             'mediagoblin/edit/change_email.html']
         assert context['form'].new_email.errors == [
-            u'Sorry, a user with that email address already exists.']
+            'Sorry, a user with that email address already exists.']
 
         # Test successful email change
         template.clear_test_template_context()
@@ -147,7 +147,7 @@ class TestUserEdit(object):
         assert email_context['verification_url'].encode('ascii') in message.get_payload(decode=True)
 
         path = urlparse.urlsplit(email_context['verification_url'])[2]
-        assert path == u'/edit/verify_email/'
+        assert path == '/edit/verify_email/'
 
         ## Try verifying with bs verification key, shouldn't work
         template.clear_test_template_context()
@@ -169,7 +169,7 @@ class TestUserEdit(object):
         # Verify email activation works
         template.clear_test_template_context()
         get_params = urlparse.urlsplit(email_context['verification_url'])[3]
-        res = test_app.get('%s?%s' % (path, get_params))
+        res = test_app.get('{}?{}'.format(path, get_params))
         res.follow()
 
         # New email saved?
@@ -181,10 +181,10 @@ class TestMetaDataEdit:
     @pytest.fixture(autouse=True)
     def setup(self, test_app):
         # set up new user
-        self.user_password = u'toast'
+        self.user_password = 'toast'
         self.user = fixture_add_user(
             password = self.user_password,
-            privileges=[u'active',u'admin']
+            privileges=['active','admin']
         )
         self.test_app = test_app
 
@@ -209,7 +209,7 @@ class TestMetaDataEdit:
     @pytest.mark.skipif(six.PY2, reason='Breaks in Python 2 but seems non-critical')
     def test_edit_metadata(self, test_app):
         media_entry = fixture_media_entry(uploader=self.user.id,
-            state=u'processed')
+            state='processed')
         media_slug = "/u/{username}/m/{media_id}/metadata/".format(
                 username = str(self.user.username),
                 media_id = str(media_entry.id))
diff --git a/mediagoblin/tests/test_exif.py b/mediagoblin/tests/test_exif.py
index ad771cca..0074e862 100644
--- a/mediagoblin/tests/test_exif.py
+++ b/mediagoblin/tests/test_exif.py
@@ -28,7 +28,7 @@ from .resources import GOOD_JPG, EMPTY_JPG, BAD_JPG, GPS_JPG, BAD_GPS_JPG
 
 
 def assert_in(a, b):
-    assert a in b, "%r not in %r" % (a, b)
+    assert a in b, "{!r} not in {!r}".format(a, b)
 
 
 def test_exif_extraction():
@@ -72,301 +72,301 @@ def test_exif_extraction():
  'EXIF CompressedBitsPerPixel': {'field_length': 8,
                                  'field_offset': 756,
                                  'field_type': 5,
-                                 'printable': u'4',
+                                 'printable': '4',
                                  'tag': 37122,
                                  'values': [[4, 1]]},
  'EXIF Contrast': {'field_length': 2,
                    'field_offset': 656,
                    'field_type': 3,
-                   'printable': u'Soft',
+                   'printable': 'Soft',
                    'tag': 41992,
                    'values': [1]},
  'EXIF CustomRendered': {'field_length': 2,
                          'field_offset': 572,
                          'field_type': 3,
-                         'printable': u'Normal',
+                         'printable': 'Normal',
                          'tag': 41985,
                          'values': [0]},
  'EXIF DateTimeDigitized': {'field_length': 20,
                             'field_offset': 736,
                             'field_type': 2,
-                            'printable': u'2011:06:22 12:20:33',
+                            'printable': '2011:06:22 12:20:33',
                             'tag': 36868,
-                            'values': u'2011:06:22 12:20:33'},
+                            'values': '2011:06:22 12:20:33'},
  'EXIF DateTimeOriginal': {'field_length': 20,
                            'field_offset': 716,
                            'field_type': 2,
-                           'printable': u'2011:06:22 12:20:33',
+                           'printable': '2011:06:22 12:20:33',
                            'tag': 36867,
-                           'values': u'2011:06:22 12:20:33'},
+                           'values': '2011:06:22 12:20:33'},
  'EXIF DigitalZoomRatio': {'field_length': 8,
                            'field_offset': 26232,
                            'field_type': 5,
-                           'printable': u'1',
+                           'printable': '1',
                            'tag': 41988,
                            'values': [[1, 1]]},
  'EXIF ExifImageLength': {'field_length': 2,
                           'field_offset': 500,
                           'field_type': 3,
-                          'printable': u'2592',
+                          'printable': '2592',
                           'tag': 40963,
                           'values': [2592]},
  'EXIF ExifImageWidth': {'field_length': 2,
                          'field_offset': 488,
                          'field_type': 3,
-                         'printable': u'3872',
+                         'printable': '3872',
                          'tag': 40962,
                          'values': [3872]},
  'EXIF ExifVersion': {'field_length': 4,
                       'field_offset': 272,
                       'field_type': 7,
-                      'printable': u'0221',
+                      'printable': '0221',
                       'tag': 36864,
                       'values': [48, 50, 50, 49]},
  'EXIF ExposureBiasValue': {'field_length': 8,
                             'field_offset': 764,
                             'field_type': 10,
-                            'printable': u'0',
+                            'printable': '0',
                             'tag': 37380,
                             'values': [[0, 1]]},
  'EXIF ExposureMode': {'field_length': 2,
                        'field_offset': 584,
                        'field_type': 3,
-                       'printable': u'Manual Exposure',
+                       'printable': 'Manual Exposure',
                        'tag': 41986,
                        'values': [1]},
  'EXIF ExposureProgram': {'field_length': 2,
                           'field_offset': 248,
                           'field_type': 3,
-                          'printable': u'Manual',
+                          'printable': 'Manual',
                           'tag': 34850,
                           'values': [1]},
  'EXIF ExposureTime': {'field_length': 8,
                        'field_offset': 700,
                        'field_type': 5,
-                       'printable': u'1/125',
+                       'printable': '1/125',
                        'tag': 33434,
                        'values': [[1, 125]]},
  'EXIF FNumber': {'field_length': 8,
                   'field_offset': 708,
                   'field_type': 5,
-                  'printable': u'10',
+                  'printable': '10',
                   'tag': 33437,
                   'values': [[10, 1]]},
  'EXIF FileSource': {'field_length': 1,
                      'field_offset': 536,
                      'field_type': 7,
-                     'printable': u'Digital Camera',
+                     'printable': 'Digital Camera',
                      'tag': 41728,
                      'values': [3]},
  'EXIF Flash': {'field_length': 2,
                 'field_offset': 380,
                 'field_type': 3,
-                'printable': u'Flash did not fire',
+                'printable': 'Flash did not fire',
                 'tag': 37385,
                 'values': [0]},
  'EXIF FlashPixVersion': {'field_length': 4,
                           'field_offset': 464,
                           'field_type': 7,
-                          'printable': u'0100',
+                          'printable': '0100',
                           'tag': 40960,
                           'values': [48, 49, 48, 48]},
  'EXIF FocalLength': {'field_length': 8,
                       'field_offset': 780,
                       'field_type': 5,
-                      'printable': u'18',
+                      'printable': '18',
                       'tag': 37386,
                       'values': [[18, 1]]},
  'EXIF FocalLengthIn35mmFilm': {'field_length': 2,
                                 'field_offset': 620,
                                 'field_type': 3,
-                                'printable': u'27',
+                                'printable': '27',
                                 'tag': 41989,
                                 'values': [27]},
  'EXIF GainControl': {'field_length': 2,
                       'field_offset': 644,
                       'field_type': 3,
-                      'printable': u'None',
+                      'printable': 'None',
                       'tag': 41991,
                       'values': [0]},
  'EXIF ISOSpeedRatings': {'field_length': 2,
                           'field_offset': 260,
                           'field_type': 3,
-                          'printable': u'100',
+                          'printable': '100',
                           'tag': 34855,
                           'values': [100]},
  'EXIF InteroperabilityOffset': {'field_length': 4,
                                  'field_offset': 512,
                                  'field_type': 4,
-                                 'printable': u'26240',
+                                 'printable': '26240',
                                  'tag': 40965,
                                  'values': [26240]},
  'EXIF LightSource': {'field_length': 2,
                       'field_offset': 368,
                       'field_type': 3,
-                      'printable': u'Unknown',
+                      'printable': 'Unknown',
                       'tag': 37384,
                       'values': [0]},
  'EXIF MaxApertureValue': {'field_length': 8,
                            'field_offset': 772,
                            'field_type': 5,
-                           'printable': u'18/5',
+                           'printable': '18/5',
                            'tag': 37381,
                            'values': [[18, 5]]},
  'EXIF MeteringMode': {'field_length': 2,
                        'field_offset': 356,
                        'field_type': 3,
-                       'printable': u'Pattern',
+                       'printable': 'Pattern',
                        'tag': 37383,
                        'values': [5]},
  'EXIF Saturation': {'field_length': 2,
                      'field_offset': 668,
                      'field_type': 3,
-                     'printable': u'Normal',
+                     'printable': 'Normal',
                      'tag': 41993,
                      'values': [0]},
  'EXIF SceneCaptureType': {'field_length': 2,
                            'field_offset': 632,
                            'field_type': 3,
-                           'printable': u'Standard',
+                           'printable': 'Standard',
                            'tag': 41990,
                            'values': [0]},
  'EXIF SceneType': {'field_length': 1,
                     'field_offset': 548,
                     'field_type': 7,
-                    'printable': u'Directly Photographed',
+                    'printable': 'Directly Photographed',
                     'tag': 41729,
                     'values': [1]},
  'EXIF SensingMethod': {'field_length': 2,
                         'field_offset': 524,
                         'field_type': 3,
-                        'printable': u'One-chip color area',
+                        'printable': 'One-chip color area',
                         'tag': 41495,
                         'values': [2]},
  'EXIF Sharpness': {'field_length': 2,
                     'field_offset': 680,
                     'field_type': 3,
-                    'printable': u'Normal',
+                    'printable': 'Normal',
                     'tag': 41994,
                     'values': [0]},
  'EXIF SubSecTime': {'field_length': 3,
                      'field_offset': 428,
                      'field_type': 2,
-                     'printable': u'10',
+                     'printable': '10',
                      'tag': 37520,
-                     'values': u'10'},
+                     'values': '10'},
  'EXIF SubSecTimeDigitized': {'field_length': 3,
                               'field_offset': 452,
                               'field_type': 2,
-                              'printable': u'10',
+                              'printable': '10',
                               'tag': 37522,
-                              'values': u'10'},
+                              'values': '10'},
  'EXIF SubSecTimeOriginal': {'field_length': 3,
                              'field_offset': 440,
                              'field_type': 2,
-                             'printable': u'10',
+                             'printable': '10',
                              'tag': 37521,
-                             'values': u'10'},
+                             'values': '10'},
  'EXIF SubjectDistanceRange': {'field_length': 2,
                                'field_offset': 692,
                                'field_type': 3,
-                               'printable': u'0',
+                               'printable': '0',
                                'tag': 41996,
                                'values': [0]},
  'EXIF WhiteBalance': {'field_length': 2,
                        'field_offset': 596,
                        'field_type': 3,
-                       'printable': u'Auto',
+                       'printable': 'Auto',
                        'tag': 41987,
                        'values': [0]},
  'Image DateTime': {'field_length': 20,
                     'field_offset': 194,
                     'field_type': 2,
-                    'printable': u'2011:06:22 12:20:33',
+                    'printable': '2011:06:22 12:20:33',
                     'tag': 306,
-                    'values': u'2011:06:22 12:20:33'},
+                    'values': '2011:06:22 12:20:33'},
  'Image ExifOffset': {'field_length': 4,
                       'field_offset': 126,
                       'field_type': 4,
-                      'printable': u'214',
+                      'printable': '214',
                       'tag': 34665,
                       'values': [214]},
  'Image Make': {'field_length': 18,
                 'field_offset': 134,
                 'field_type': 2,
-                'printable': u'NIKON CORPORATION',
+                'printable': 'NIKON CORPORATION',
                 'tag': 271,
-                'values': u'NIKON CORPORATION'},
+                'values': 'NIKON CORPORATION'},
  'Image Model': {'field_length': 10,
                  'field_offset': 152,
                  'field_type': 2,
-                 'printable': u'NIKON D80',
+                 'printable': 'NIKON D80',
                  'tag': 272,
-                 'values': u'NIKON D80'},
+                 'values': 'NIKON D80'},
  'Image Orientation': {'field_length': 2,
                        'field_offset': 42,
                        'field_type': 3,
-                       'printable': u'Rotated 90 CW',
+                       'printable': 'Rotated 90 CW',
                        'tag': 274,
                        'values': [6]},
  'Image ResolutionUnit': {'field_length': 2,
                           'field_offset': 78,
                           'field_type': 3,
-                          'printable': u'Pixels/Inch',
+                          'printable': 'Pixels/Inch',
                           'tag': 296,
                           'values': [2]},
  'Image Software': {'field_length': 15,
                     'field_offset': 178,
                     'field_type': 2,
-                    'printable': u'Shotwell 0.9.3',
+                    'printable': 'Shotwell 0.9.3',
                     'tag': 305,
-                    'values': u'Shotwell 0.9.3'},
+                    'values': 'Shotwell 0.9.3'},
  'Image XResolution': {'field_length': 8,
                        'field_offset': 162,
                        'field_type': 5,
-                       'printable': u'300',
+                       'printable': '300',
                        'tag': 282,
                        'values': [[300, 1]]},
  'Image YCbCrPositioning': {'field_length': 2,
                             'field_offset': 114,
                             'field_type': 3,
-                            'printable': u'Co-sited',
+                            'printable': 'Co-sited',
                             'tag': 531,
                             'values': [2]},
  'Image YResolution': {'field_length': 8,
                        'field_offset': 170,
                        'field_type': 5,
-                       'printable': u'300',
+                       'printable': '300',
                        'tag': 283,
                        'values': [[300, 1]]},
  'Thumbnail Compression': {'field_length': 2,
                            'field_offset': 26280,
                            'field_type': 3,
-                           'printable': u'JPEG (old-style)',
+                           'printable': 'JPEG (old-style)',
                            'tag': 259,
                            'values': [6]},
  'Thumbnail ResolutionUnit': {'field_length': 2,
                               'field_offset': 26316,
                               'field_type': 3,
-                              'printable': u'Pixels/Inch',
+                              'printable': 'Pixels/Inch',
                               'tag': 296,
                               'values': [2]},
  'Thumbnail XResolution': {'field_length': 8,
                            'field_offset': 26360,
                            'field_type': 5,
-                           'printable': u'300',
+                           'printable': '300',
                            'tag': 282,
                            'values': [[300, 1]]},
  'Thumbnail YCbCrPositioning': {'field_length': 2,
                                 'field_offset': 26352,
                                 'field_type': 3,
-                                'printable': u'Co-sited',
+                                'printable': 'Co-sited',
                                 'tag': 531,
                                 'values': [2]},
  'Thumbnail YResolution': {'field_length': 8,
                            'field_offset': 26368,
                            'field_type': 5,
-                           'printable': u'300',
+                           'printable': '300',
                            'tag': 283,
                            'values': [[300, 1]]}})
 
diff --git a/mediagoblin/tests/test_globals.py b/mediagoblin/tests/test_globals.py
index fe3088f8..2b7212e9 100644
--- a/mediagoblin/tests/test_globals.py
+++ b/mediagoblin/tests/test_globals.py
@@ -19,7 +19,7 @@ import pytest
 from mediagoblin import mg_globals
 
 
-class TestGlobals(object):
+class TestGlobals:
     def setup(self):
         self.old_database = mg_globals.database
 
diff --git a/mediagoblin/tests/test_ldap.py b/mediagoblin/tests/test_ldap.py
index 6ac0fc46..0af1b7f5 100644
--- a/mediagoblin/tests/test_ldap.py
+++ b/mediagoblin/tests/test_ldap.py
@@ -18,7 +18,7 @@ import pkg_resources
 import pytest
 import six
 try:
-    import mock
+    from unittest import mock
 except ImportError:
     import unittest.mock as mock
 
@@ -43,7 +43,7 @@ def ldap_plugin_app(request):
 
 
 def return_value():
-    return u'chris', u'chris@example.com'
+    return 'chris', 'chris@example.com'
 
 
 def test_ldap_plugin(ldap_plugin_app):
@@ -65,8 +65,8 @@ def test_ldap_plugin(ldap_plugin_app):
 
     context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
     form = context['login_form']
-    assert form.username.errors == [u'This field is required.']
-    assert form.password.errors == [u'This field is required.']
+    assert form.username.errors == ['This field is required.']
+    assert form.password.errors == ['This field is required.']
 
     @mock.patch('mediagoblin.plugins.ldap.tools.LDAP.login',
                 mock.Mock(return_value=return_value()))
@@ -74,21 +74,21 @@ def test_ldap_plugin(ldap_plugin_app):
         template.clear_test_template_context()
         res = ldap_plugin_app.post(
             '/auth/ldap/login/',
-            {'username': u'chris',
-             'password': u'toast'})
+            {'username': 'chris',
+             'password': 'toast'})
 
         context = template.TEMPLATE_TEST_CONTEXT[
             'mediagoblin/auth/register.html']
         register_form = context['register_form']
 
-        assert register_form.username.data == u'chris'
-        assert register_form.email.data == u'chris@example.com'
+        assert register_form.username.data == 'chris'
+        assert register_form.email.data == 'chris@example.com'
 
         template.clear_test_template_context()
         res = ldap_plugin_app.post(
             '/auth/ldap/register/',
-            {'username': u'chris',
-             'email': u'chris@example.com'})
+            {'username': 'chris',
+             'email': 'chris@example.com'})
         res.follow()
 
         assert urlparse.urlsplit(res.location)[2] == '/u/chris/'
@@ -99,24 +99,24 @@ def test_ldap_plugin(ldap_plugin_app):
         template.clear_test_template_context()
         res = ldap_plugin_app.post(
             '/auth/ldap/register/',
-            {'username': u'chris',
-             'email': u'chris@example.com'})
+            {'username': 'chris',
+             'email': 'chris@example.com'})
 
         context = template.TEMPLATE_TEST_CONTEXT[
             'mediagoblin/auth/register.html']
         register_form = context['register_form']
 
         assert register_form.email.errors == [
-            u'Sorry, a user with that email address already exists.']
+            'Sorry, a user with that email address already exists.']
         assert register_form.username.errors == [
-            u'Sorry, a user with that name already exists.']
+            'Sorry, a user with that name already exists.']
 
         # Log out
         ldap_plugin_app.get('/auth/logout/')
 
         # Get user and detach from session
         test_user = mg_globals.database.LocalUser.query.filter(
-            LocalUser.username==u'chris'
+            LocalUser.username=='chris'
         ).first()
         Session.expunge(test_user)
 
@@ -124,8 +124,8 @@ def test_ldap_plugin(ldap_plugin_app):
         template.clear_test_template_context()
         res = ldap_plugin_app.post(
             '/auth/ldap/login/',
-            {'username': u'chris',
-             'password': u'toast'})
+            {'username': 'chris',
+             'password': 'toast'})
         res.follow()
 
         assert urlparse.urlsplit(res.location)[2] == '/'
@@ -134,6 +134,6 @@ def test_ldap_plugin(ldap_plugin_app):
         # Make sure user is in the session
         context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
         session = context['request'].session
-        assert session['user_id'] == six.text_type(test_user.id)
+        assert session['user_id'] == str(test_user.id)
 
     _test_authentication()
diff --git a/mediagoblin/tests/test_legacy_api.py b/mediagoblin/tests/test_legacy_api.py
index b3b2fcec..93f016a3 100644
--- a/mediagoblin/tests/test_legacy_api.py
+++ b/mediagoblin/tests/test_legacy_api.py
@@ -31,13 +31,13 @@ from .resources import GOOD_JPG, GOOD_PNG, EVIL_FILE, EVIL_JPG, EVIL_PNG, \
 _log = logging.getLogger(__name__)
 
 
-class TestAPI(object):
+class TestAPI:
     def setup(self):
         self.db = mg_globals.database
 
-        self.user_password = u'4cc355_70k3N'
-        self.user = fixture_add_user(u'joapi', self.user_password,
-            privileges=[u'active',u'uploader'])
+        self.user_password = '4cc355_70k3N'
+        self.user = fixture_add_user('joapi', self.user_password,
+            privileges=['active','uploader'])
 
     def login(self, test_app):
         test_app.post(
@@ -49,7 +49,7 @@ class TestAPI(object):
         return template.TEMPLATE_TEST_CONTEXT[template_name]
 
     def http_auth_headers(self):
-        return {'Authorization': ('Basic {0}'.format(
+        return {'Authorization': ('Basic {}'.format(
                 base64.b64encode((':'.join([
                     self.user.username,
                     self.user_password])).encode('ascii')).decode()))}
@@ -91,4 +91,4 @@ class TestAPI(object):
 
         assert response.status_int == 200
 
-        assert self.db.MediaEntry.query.filter_by(title=u'Great JPG!').first()
+        assert self.db.MediaEntry.query.filter_by(title='Great JPG!').first()
diff --git a/mediagoblin/tests/test_metadata.py b/mediagoblin/tests/test_metadata.py
index a10e00ec..b185e605 100644
--- a/mediagoblin/tests/test_metadata.py
+++ b/mediagoblin/tests/test_metadata.py
@@ -39,7 +39,7 @@ class TestMetadataFunctionality:
         # Free floating nodes should be removed
         assert jsonld_metadata.get('location') is None
         assert jsonld_metadata.get('@context') == \
-            u"http://www.w3.org/2013/json-ld-context/rdfa11"
+            "http://www.w3.org/2013/json-ld-context/rdfa11"
 
         # Next, make sure that various badly formatted metadata
         # will be rejected.
diff --git a/mediagoblin/tests/test_misc.py b/mediagoblin/tests/test_misc.py
index 5500a0d7..fc1057a3 100644
--- a/mediagoblin/tests/test_misc.py
+++ b/mediagoblin/tests/test_misc.py
@@ -34,8 +34,8 @@ def test_404_for_non_existent(test_app):
 
 
 def test_user_deletes_other_comments(test_app):
-    user_a = fixture_add_user(u"chris_a")
-    user_b = fixture_add_user(u"chris_b")
+    user_a = fixture_add_user("chris_a")
+    user_b = fixture_add_user("chris_b")
 
     media_a = fixture_media_entry(uploader=user_a.id, save=False,
                                   expunge=False, fake_upload=False)
@@ -50,7 +50,7 @@ def test_user_deletes_other_comments(test_app):
         for m in (media_a, media_b):
             cmt = TextComment()
             cmt.actor = u.id
-            cmt.content = u"Some Comment"
+            cmt.content = "Some Comment"
             Session.add(cmt)
             # think i need this to get the command ID
             Session.flush()
@@ -94,12 +94,12 @@ def test_user_deletes_other_comments(test_app):
 
 
 def test_media_deletes_broken_attachment(test_app):
-    user_a = fixture_add_user(u"chris_a")
+    user_a = fixture_add_user("chris_a")
 
     media = fixture_media_entry(uploader=user_a.id, save=False, expunge=False)
     media.attachment_files.append(dict(
-            name=u"some name",
-            filepath=[u"does", u"not", u"exist"],
+            name="some name",
+            filepath=["does", "not", "exist"],
             ))
     Session.add(media)
     Session.flush()
@@ -151,7 +151,7 @@ def test_comments_removed_when_graveyarded(test_app):
     # Add the TextComment
     comment = TextComment()
     comment.actor = user.id
-    comment.content = u"This is a comment that will be deleted."
+    comment.content = "This is a comment that will be deleted."
     comment.save()
 
     # Add a link for the comment
diff --git a/mediagoblin/tests/test_modelmethods.py b/mediagoblin/tests/test_modelmethods.py
index 4c66e27b..63c38660 100644
--- a/mediagoblin/tests/test_modelmethods.py
+++ b/mediagoblin/tests/test_modelmethods.py
@@ -17,7 +17,6 @@
 # Maybe not every model needs a test, but some models have special
 # methods, and so it makes sense to test them here.
 
-from __future__ import print_function
 
 from mediagoblin.db.base import Session
 from mediagoblin.db.models import MediaEntry, User, LocalUser, Privilege, \
@@ -28,13 +27,13 @@ from mediagoblin.tests.tools import fixture_add_user, fixture_media_entry, \
                                     fixture_add_activity
 
 try:
-    import mock
+    from unittest import mock
 except ImportError:
     import unittest.mock as mock
 import pytest
 
 
-class FakeUUID(object):
+class FakeUUID:
     hex = 'testtest-test-test-test-testtesttest'
 
 UUID_MOCK = mock.Mock(return_value=FakeUUID())
@@ -42,22 +41,22 @@ UUID_MOCK = mock.Mock(return_value=FakeUUID())
 REQUEST_CONTEXT = ['mediagoblin/root.html', 'request']
 
 
-class TestMediaEntrySlugs(object):
+class TestMediaEntrySlugs:
     def _setup(self):
-        self.chris_user = fixture_add_user(u'chris')
-        self.emily_user = fixture_add_user(u'emily')
+        self.chris_user = fixture_add_user('chris')
+        self.emily_user = fixture_add_user('emily')
         self.existing_entry = self._insert_media_entry_fixture(
-            title=u"Beware, I exist!",
-            slug=u"beware-i-exist")
+            title="Beware, I exist!",
+            slug="beware-i-exist")
 
     def _insert_media_entry_fixture(self, title=None, slug=None, this_id=None,
                                     uploader=None, save=True):
         entry = MediaEntry()
-        entry.title = title or u"Some title"
+        entry.title = title or "Some title"
         entry.slug = slug
         entry.id = this_id
         entry.actor = uploader or self.chris_user.id
-        entry.media_type = u'image'
+        entry.media_type = 'image'
 
         if save:
             entry.save()
@@ -67,33 +66,33 @@ class TestMediaEntrySlugs(object):
     def test_unique_slug_from_title(self, test_app):
         self._setup()
 
-        entry = self._insert_media_entry_fixture(u"Totally unique slug!", save=False)
+        entry = self._insert_media_entry_fixture("Totally unique slug!", save=False)
         entry.generate_slug()
-        assert entry.slug == u'totally-unique-slug'
+        assert entry.slug == 'totally-unique-slug'
 
     def test_old_good_unique_slug(self, test_app):
         self._setup()
 
         entry = self._insert_media_entry_fixture(
-            u"A title here", u"a-different-slug-there", save=False)
+            "A title here", "a-different-slug-there", save=False)
         entry.generate_slug()
-        assert entry.slug == u"a-different-slug-there"
+        assert entry.slug == "a-different-slug-there"
 
     def test_old_weird_slug(self, test_app):
         self._setup()
 
         entry = self._insert_media_entry_fixture(
-            slug=u"wowee!!!!!", save=False)
+            slug="wowee!!!!!", save=False)
         entry.generate_slug()
-        assert entry.slug == u"wowee"
+        assert entry.slug == "wowee"
 
     def test_existing_slug_use_id(self, test_app):
         self._setup()
 
         entry = self._insert_media_entry_fixture(
-            u"Beware, I exist!!", this_id=9000, save=False)
+            "Beware, I exist!!", this_id=9000, save=False)
         entry.generate_slug()
-        assert entry.slug == u"beware-i-exist-9000"
+        assert entry.slug == "beware-i-exist-9000"
 
     def test_existing_slug_cant_use_id(self, test_app):
         self._setup()
@@ -104,12 +103,12 @@ class TestMediaEntrySlugs(object):
         def _real_test():
             # This one grabs the nine thousand slug
             self._insert_media_entry_fixture(
-                slug=u"beware-i-exist-9000")
+                slug="beware-i-exist-9000")
 
             entry = self._insert_media_entry_fixture(
-                u"Beware, I exist!!", this_id=9000, save=False)
+                "Beware, I exist!!", this_id=9000, save=False)
             entry.generate_slug()
-            assert entry.slug == u"beware-i-exist-test"
+            assert entry.slug == "beware-i-exist-test"
 
         _real_test()
 
@@ -122,16 +121,16 @@ class TestMediaEntrySlugs(object):
         def _real_test():
             # This one grabs the nine thousand slug
             self._insert_media_entry_fixture(
-                slug=u"beware-i-exist-9000")
+                slug="beware-i-exist-9000")
 
             # This one grabs makes sure the annoyance doesn't stop
             self._insert_media_entry_fixture(
-                slug=u"beware-i-exist-test")
+                slug="beware-i-exist-test")
 
             entry = self._insert_media_entry_fixture(
-                 u"Beware, I exist!!", this_id=9000, save=False)
+                 "Beware, I exist!!", this_id=9000, save=False)
             entry.generate_slug()
-            assert entry.slug == u"beware-i-exist-testtest"
+            assert entry.slug == "beware-i-exist-testtest"
 
         _real_test()
 
@@ -141,42 +140,42 @@ class TestMediaEntrySlugs(object):
         all.  We'll just reference them by id.
 
                   ,
-                 / \      (@!#?@!)
-                |\,/|   ,-,  /
+                 / \\      (@!#?@!)
+                |\\,/|   ,-,  /
                 | |#|  ( ")~
-               / \|/ \  L L
-              |\,/|\,/|
+               / \\|/ \\  L L
+              |\\,/|\\,/|
               | |#, |#|
-             / \|/ \|/ \
-            |\,/|\,/|\,/|
+             / \\|/ \\|/ \
+            |\\,/|\\,/|\\,/|
             | |#| |#| |#|
-           / \|/ \|/ \|/ \
-          |\,/|\,/|\,/|\,/|
+           / \\|/ \\|/ \\|/ \
+          |\\,/|\\,/|\\,/|\\,/|
           | |#| |#| |#| |#|
-           \|/ \|/ \|/ \|/
+           \\|/ \\|/ \\|/ \\|/
         """
         self._setup()
 
         qbert_entry = self._insert_media_entry_fixture(
-            u"@!#?@!", save=False)
+            "@!#?@!", save=False)
         qbert_entry.generate_slug()
         assert qbert_entry.slug is None
 
 class TestUserHasPrivilege:
     def _setup(self):
-        fixture_add_user(u'natalie',
-            privileges=[u'admin',u'moderator',u'active'])
-        fixture_add_user(u'aeva',
-            privileges=[u'moderator',u'active'])
+        fixture_add_user('natalie',
+            privileges=['admin','moderator','active'])
+        fixture_add_user('aeva',
+            privileges=['moderator','active'])
         self.natalie_user = LocalUser.query.filter(
-            LocalUser.username==u'natalie').first()
+            LocalUser.username=='natalie').first()
         self.aeva_user = LocalUser.query.filter(
-            LocalUser.username==u'aeva').first()
+            LocalUser.username=='aeva').first()
 
     def test_privilege_added_correctly(self, test_app):
         self._setup()
         admin = Privilege.query.filter(
-            Privilege.privilege_name == u'admin').one()
+            Privilege.privilege_name == 'admin').one()
         # first make sure the privileges were added successfully
 
         assert admin in self.natalie_user.all_privileges
@@ -186,23 +185,23 @@ class TestUserHasPrivilege:
         self._setup()
 
         # then test out the user.has_privilege method for one privilege
-        assert not self.aeva_user.has_privilege(u'admin')
-        assert self.natalie_user.has_privilege(u'active')
+        assert not self.aeva_user.has_privilege('admin')
+        assert self.natalie_user.has_privilege('active')
 
     def test_allow_admin(self, test_app):
         self._setup()
 
         # This should work because she is an admin.
-        assert self.natalie_user.has_privilege(u'commenter')
+        assert self.natalie_user.has_privilege('commenter')
 
         # Test that we can look this out ignoring that she's an admin
-        assert not self.natalie_user.has_privilege(u'commenter', allow_admin=False)
+        assert not self.natalie_user.has_privilege('commenter', allow_admin=False)
 
 def test_media_data_init(test_app):
     Session.rollback()
     Session.remove()
     media = MediaEntry()
-    media.media_type = u"mediagoblin.media_types.image"
+    media.media_type = "mediagoblin.media_types.image"
     assert media.media_data is None
     media.media_data_init()
     assert media.media_data is not None
@@ -215,12 +214,12 @@ def test_media_data_init(test_app):
 
 class TestUserUrlForSelf(MGClientTestCase):
 
-    usernames = [(u'lindsay', dict(privileges=[u'active']))]
+    usernames = [('lindsay', dict(privileges=['active']))]
 
     def test_url_for_self(self):
         _, request = self.do_get('/', *REQUEST_CONTEXT)
 
-        assert self.user(u'lindsay').url_for_self(request.urlgen) == '/u/lindsay/'
+        assert self.user('lindsay').url_for_self(request.urlgen) == '/u/lindsay/'
 
     def test_url_for_self_not_callable(self):
         _, request = self.do_get('/', *REQUEST_CONTEXT)
@@ -229,6 +228,6 @@ class TestUserUrlForSelf(MGClientTestCase):
             pass
 
         with pytest.raises(TypeError) as excinfo:
-            self.user(u'lindsay').url_for_self(fake_urlgen())
+            self.user('lindsay').url_for_self(fake_urlgen())
         assert excinfo.errisinstance(TypeError)
         assert 'object is not callable' in str(excinfo)
diff --git a/mediagoblin/tests/test_moderation.py b/mediagoblin/tests/test_moderation.py
index 55bb4c4b..c262a768 100644
--- a/mediagoblin/tests/test_moderation.py
+++ b/mediagoblin/tests/test_moderation.py
@@ -28,12 +28,12 @@ class TestModerationViews:
     def _setup(self, test_app):
         self.test_app = test_app
 
-        fixture_add_user(u'admin',
-            privileges=[u'admin',u'active'])
-        fixture_add_user(u'moderator',
-            privileges=[u'moderator',u'active'])
-        fixture_add_user(u'regular',
-            privileges=[u'active',u'commenter'])
+        fixture_add_user('admin',
+            privileges=['admin','active'])
+        fixture_add_user('moderator',
+            privileges=['moderator','active'])
+        fixture_add_user('regular',
+            privileges=['active','commenter'])
         self.query_for_users()
 
     def login(self, username):
@@ -48,9 +48,9 @@ class TestModerationViews:
         self.query_for_users()
 
     def query_for_users(self):
-        self.admin_user = LocalUser.query.filter(LocalUser.username==u'admin').first()
-        self.mod_user = LocalUser.query.filter(LocalUser.username==u'moderator').first()
-        self.user = LocalUser.query.filter(LocalUser.username==u'regular').first()
+        self.admin_user = LocalUser.query.filter(LocalUser.username=='admin').first()
+        self.mod_user = LocalUser.query.filter(LocalUser.username=='moderator').first()
+        self.user = LocalUser.query.filter(LocalUser.username=='regular').first()
 
     def do_post(self, data, *context_keys, **kwargs):
         url = kwargs.pop('url', '/submit/')
@@ -65,39 +65,39 @@ class TestModerationViews:
         return response, context_data
 
     def testGiveOrTakeAwayPrivileges(self):
-        self.login(u'admin')
+        self.login('admin')
         # First, test an admin taking away a privilege from a user
         #----------------------------------------------------------------------
-        response, context = self.do_post({'privilege_name':u'commenter'},
-            url='/mod/users/{0}/privilege/'.format(self.user.username))
+        response, context = self.do_post({'privilege_name':'commenter'},
+            url='/mod/users/{}/privilege/'.format(self.user.username))
         assert response.status == '302 FOUND'
         self.query_for_users()
-        assert not self.user.has_privilege(u'commenter')
+        assert not self.user.has_privilege('commenter')
 
         # Then, test an admin giving a privilege to a user
         #----------------------------------------------------------------------
-        response, context = self.do_post({'privilege_name':u'commenter'},
-            url='/mod/users/{0}/privilege/'.format(self.user.username))
+        response, context = self.do_post({'privilege_name':'commenter'},
+            url='/mod/users/{}/privilege/'.format(self.user.username))
         assert response.status == '302 FOUND'
         self.query_for_users()
-        assert self.user.has_privilege(u'commenter')
+        assert self.user.has_privilege('commenter')
 
         # Then, test a mod trying to take away a privilege from a user
         # they are not allowed to do this, so this will raise an error
         #----------------------------------------------------------------------
         self.logout()
-        self.login(u'moderator')
+        self.login('moderator')
 
         with pytest.raises(AppError) as excinfo:
-            response, context = self.do_post({'privilege_name':u'commenter'},
-                url='/mod/users/{0}/privilege/'.format(self.user.username))
+            response, context = self.do_post({'privilege_name':'commenter'},
+                url='/mod/users/{}/privilege/'.format(self.user.username))
         assert 'Bad response: 403 FORBIDDEN' in str(excinfo)
         self.query_for_users()
 
-        assert self.user.has_privilege(u'commenter')
+        assert self.user.has_privilege('commenter')
 
     def testReportResolution(self):
-        self.login(u'moderator')
+        self.login('moderator')
 
         # First, test a moderators taking away a user's privilege in response
         # to a reported comment
@@ -106,23 +106,23 @@ class TestModerationViews:
         comment_report = Report.query.filter(
             Report.reported_user==self.user).first()
 
-        response = self.test_app.get('/mod/reports/{0}/'.format(
+        response = self.test_app.get('/mod/reports/{}/'.format(
             comment_report.id))
         assert response.status == '200 OK'
         self.query_for_users()
         comment_report = Report.query.filter(
             Report.reported_user==self.user).first()
 
-        response, context = self.do_post({'action_to_resolve':[u'takeaway'],
-            'take_away_privileges':[u'commenter'],
+        response, context = self.do_post({'action_to_resolve':['takeaway'],
+            'take_away_privileges':['commenter'],
             'targeted_user':self.user.id},
-            url='/mod/reports/{0}/'.format(comment_report.id))
+            url='/mod/reports/{}/'.format(comment_report.id))
 
         self.query_for_users()
         comment_report = Report.query.filter(
             Report.reported_user==self.user).first()
         assert response.status == '302 FOUND'
-        assert not self.user.has_privilege(u'commenter')
+        assert not self.user.has_privilege('commenter')
         assert comment_report.is_archived_report() is True
 
         fixture_add_comment_report(reported_user=self.user)
@@ -134,16 +134,16 @@ class TestModerationViews:
         #----------------------------------------------------------------------
         self.query_for_users()
 
-        response, context = self.do_post({'action_to_resolve':[u'sendmessage'],
+        response, context = self.do_post({'action_to_resolve':['sendmessage'],
             'message_to_user':'This is your last warning, regular....',
             'targeted_user':self.user.id},
-            url='/mod/reports/{0}/'.format(comment_report.id))
+            url='/mod/reports/{}/'.format(comment_report.id))
 
         self.query_for_users()
         comment_report = Report.query.filter(
             Report.reported_user==self.user).first()
         assert response.status == '302 FOUND'
-        assert mail.EMAIL_TEST_MBOX_INBOX ==  [{'to': [u'regular@example.com'],
+        assert mail.EMAIL_TEST_MBOX_INBOX ==  [{'to': ['regular@example.com'],
             'message': 'Content-Type: text/plain; charset="utf-8"\n\
 MIME-Version: 1.0\nContent-Transfer-Encoding: base64\nSubject: Warning from- \
 moderator \nFrom: notice@mediagoblin.example.org\nTo: regular@example.com\n\n\
@@ -157,7 +157,7 @@ VGhpcyBpcyB5b3VyIGxhc3Qgd2FybmluZywgcmVndWxhci4uLi4=\n',
         #----------------------------------------------------------------------
         self.query_for_users()
         fixture_add_comment(author=self.user.id,
-            comment=u'Comment will be removed')
+            comment='Comment will be removed')
         test_comment = TextComment.query.filter(
             TextComment.actor==self.user.id).first()
         fixture_add_comment_report(comment=test_comment,
@@ -171,11 +171,11 @@ VGhpcyBpcyB5b3VyIGxhc3Qgd2FybmluZywgcmVndWxhci4uLi4=\n',
             Report.resolved==None).first()
 
         response, context = self.do_post(
-            {'action_to_resolve':[u'userban', u'delete'],
+            {'action_to_resolve':['userban', 'delete'],
             'targeted_user':self.user.id,
-            'why_user_was_banned':u'',
-            'user_banned_until':u''},
-            url='/mod/reports/{0}/'.format(comment_report.id))
+            'why_user_was_banned':'',
+            'user_banned_until':''},
+            url='/mod/reports/{}/'.format(comment_report.id))
         assert response.status == '302 FOUND'
         self.query_for_users()
         test_user_ban = UserBan.query.filter(
@@ -193,17 +193,17 @@ VGhpcyBpcyB5b3VyIGxhc3Qgd2FybmluZywgcmVndWxhci4uLi4=\n',
             Report.reported_user==self.admin_user).filter(
             Report.resolved==None).first()
 
-        response, context = self.do_post({'action_to_resolve':[u'takeaway'],
-            'take_away_privileges':[u'active'],
+        response, context = self.do_post({'action_to_resolve':['takeaway'],
+            'take_away_privileges':['active'],
             'targeted_user':self.admin_user.id},
-            url='/mod/reports/{0}/'.format(comment_report.id))
+            url='/mod/reports/{}/'.format(comment_report.id))
         self.query_for_users()
 
         assert response.status == '200 OK'
-        assert self.admin_user.has_privilege(u'active')
+        assert self.admin_user.has_privilege('active')
 
     def testAllModerationViews(self):
-        self.login(u'moderator')
+        self.login('moderator')
         username = self.user.username
         self.query_for_users()
         fixture_add_comment_report(reported_user=self.admin_user)
@@ -216,7 +216,7 @@ VGhpcyBpcyB5b3VyIGxhc3Qgd2FybmluZywgcmVndWxhci4uLi4=\n',
         response = self.test_app.get('/mod/users/')
         assert response.status == "200 OK"
 
-        user_page_url = '/mod/users/{0}/'.format(username)
+        user_page_url = '/mod/users/{}/'.format(username)
         response = self.test_app.get(user_page_url)
         assert response.status == "200 OK"
 
@@ -224,20 +224,20 @@ VGhpcyBpcyB5b3VyIGxhc3Qgd2FybmluZywgcmVndWxhci4uLi4=\n',
         assert response.status == "200 OK"
 
     def testBanUnBanUser(self):
-        self.login(u'admin')
+        self.login('admin')
         username = self.user.username
         user_id = self.user.id
-        ban_url = '/mod/users/{0}/ban/'.format(username)
+        ban_url = '/mod/users/{}/ban/'.format(username)
         response, context = self.do_post({
-            'user_banned_until':u'',
-            'why_user_was_banned':u'Because I said so'},
+            'user_banned_until':'',
+            'why_user_was_banned':'Because I said so'},
             url=ban_url)
 
         assert response.status == "302 FOUND"
         user_banned = UserBan.query.filter(UserBan.user_id==user_id).first()
         assert user_banned is not None
         assert user_banned.expiration_date is None
-        assert user_banned.reason == u'Because I said so'
+        assert user_banned.reason == 'Because I said so'
 
         response, context = self.do_post({},
             url=ban_url)
diff --git a/mediagoblin/tests/test_notifications.py b/mediagoblin/tests/test_notifications.py
index 776bfc71..2bdc4223 100644
--- a/mediagoblin/tests/test_notifications.py
+++ b/mediagoblin/tests/test_notifications.py
@@ -37,13 +37,13 @@ class TestNotifications:
 
         # TODO: Possibly abstract into a decorator like:
         # @as_authenticated_user('chris')
-        self.test_user = fixture_add_user(privileges=[u'active',u'commenter'])
+        self.test_user = fixture_add_user(privileges=['active','commenter'])
 
         self.current_user = None
 
         self.login()
 
-    def login(self, username=u'chris', password=u'toast'):
+    def login(self, username='chris', password='toast'):
         response = self.test_app.post(
             '/auth/login/', {
                 'username': username,
@@ -75,13 +75,13 @@ class TestNotifications:
         '''
         user = fixture_add_user('otherperson', password='nosreprehto',
                                 wants_comment_notification=wants_email,
-                                privileges=[u'active',u'commenter'])
+                                privileges=['active','commenter'])
 
         assert user.wants_comment_notification == wants_email
 
         user_id = user.id
 
-        media_entry = fixture_media_entry(uploader=user.id, state=u'processed')
+        media_entry = fixture_media_entry(uploader=user.id, state='processed')
 
         media_entry_id = media_entry.id
 
@@ -89,15 +89,15 @@ class TestNotifications:
 
         subscription_id = subscription.id
 
-        media_uri_id = '/u/{0}/m/{1}/'.format(user.username,
+        media_uri_id = '/u/{}/m/{}/'.format(user.username,
                                               media_entry.id)
-        media_uri_slug = '/u/{0}/m/{1}/'.format(user.username,
+        media_uri_slug = '/u/{}/m/{}/'.format(user.username,
                                                 media_entry.slug)
 
         self.test_app.post(
             media_uri_id + 'comment/add/',
             {
-                'comment_content': u'Test comment #42'
+                'comment_content': 'Test comment #42'
             }
         )
 
@@ -111,7 +111,7 @@ class TestNotifications:
         assert notification.seen == False
         assert notification.user_id == user.id
         assert notification.obj().comment().get_actor.id == self.test_user.id
-        assert notification.obj().comment().content == u'Test comment #42'
+        assert notification.obj().comment().content == 'Test comment #42'
 
         if wants_email == True:
             # Why the `or' here?  In Werkzeug 0.11.0 and above
@@ -127,7 +127,7 @@ charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: \
 base64\nSubject: GNU MediaGoblin - chris commented on your \
 post\nFrom: notice@mediagoblin.example.org\nTo: \
 otherperson@example.com\n\nSGkgb3RoZXJwZXJzb24sCmNocmlzIGNvbW1lbnRlZCBvbiB5b3VyIHBvc3QgKGh0dHA6Ly9sb2Nh\nbGhvc3Q6ODAvdS9vdGhlcnBlcnNvbi9tL3NvbWUtdGl0bGUvYy8xLyNjb21tZW50KSBhdCBHTlUg\nTWVkaWFHb2JsaW4KClRlc3QgY29tbWVudCAjNDIKCkdOVSBNZWRpYUdvYmxpbg==\n',
-                     'to': [u'otherperson@example.com']}]
+                     'to': ['otherperson@example.com']}]
                 or mail.EMAIL_TEST_MBOX_INBOX == [
                     {'from': 'notice@mediagoblin.example.org',
                      'message': 'Content-Type: text/plain; \
@@ -135,7 +135,7 @@ charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: \
 base64\nSubject: GNU MediaGoblin - chris commented on your \
 post\nFrom: notice@mediagoblin.example.org\nTo: \
 otherperson@example.com\n\nSGkgb3RoZXJwZXJzb24sCmNocmlzIGNvbW1lbnRlZCBvbiB5b3VyIHBvc3QgKGh0dHA6Ly9sb2Nh\nbGhvc3QvdS9vdGhlcnBlcnNvbi9tL3NvbWUtdGl0bGUvYy8xLyNjb21tZW50KSBhdCBHTlUgTWVk\naWFHb2JsaW4KClRlc3QgY29tbWVudCAjNDIKCkdOVSBNZWRpYUdvYmxpbg==\n',
-                     'to': [u'otherperson@example.com']}])
+                     'to': ['otherperson@example.com']}])
         else:
             assert mail.EMAIL_TEST_MBOX_INBOX == []
 
@@ -147,7 +147,7 @@ otherperson@example.com\n\nSGkgb3RoZXJwZXJzb24sCmNocmlzIGNvbW1lbnRlZCBvbiB5b3VyI
         self.logout()
         self.login('otherperson', 'nosreprehto')
 
-        self.test_app.get(media_uri_slug + 'c/{0}/'.format(comment_id))
+        self.test_app.get(media_uri_slug + 'c/{}/'.format(comment_id))
 
         notification = Notification.query.filter_by(id=notification_id).first()
 
@@ -170,27 +170,27 @@ otherperson@example.com\n\nSGkgb3RoZXJwZXJzb24sCmNocmlzIGNvbW1lbnRlZCBvbiB5b3VyI
         """ Test that mark_all_comments_seen works"""
 
         user = fixture_add_user('otherperson', password='nosreprehto',
-                        privileges=[u'active'])
+                        privileges=['active'])
 
-        media_entry = fixture_media_entry(uploader=user.id, state=u'processed')
+        media_entry = fixture_media_entry(uploader=user.id, state='processed')
 
         fixture_comment_subscription(media_entry)
 
-        media_uri_id = '/u/{0}/m/{1}/'.format(user.username,
+        media_uri_id = '/u/{}/m/{}/'.format(user.username,
                                               media_entry.id)
 
         # add 2 comments
         self.test_app.post(
             media_uri_id + 'comment/add/',
             {
-                'comment_content': u'Test comment #43'
+                'comment_content': 'Test comment #43'
             }
         )
 
         self.test_app.post(
             media_uri_id + 'comment/add/',
             {
-                'comment_content': u'Test comment #44'
+                'comment_content': 'Test comment #44'
             }
         )
 
diff --git a/mediagoblin/tests/test_oauth1.py b/mediagoblin/tests/test_oauth1.py
index e41a68c7..fc4a2b01 100644
--- a/mediagoblin/tests/test_oauth1.py
+++ b/mediagoblin/tests/test_oauth1.py
@@ -25,7 +25,7 @@ from mediagoblin.tools import template, pluginapi
 from mediagoblin.tests.tools import fixture_add_user
 
 
-class TestOAuth(object):
+class TestOAuth:
 
     MIME_FORM = "application/x-www-form-urlencoded"
     MIME_JSON = "application/json"
@@ -123,7 +123,7 @@ class TestOAuth(object):
     def to_authorize_headers(self, data):
         headers = ""
         for key, value in data.items():
-            headers += '{0}="{1}",'.format(key, value)
+            headers += '{}="{}",'.format(key, value)
         return {"Authorization": "OAuth " + headers[:-1]}
 
     def test_request_token(self):
diff --git a/mediagoblin/tests/test_openid.py b/mediagoblin/tests/test_openid.py
index 71767032..a0d129a9 100644
--- a/mediagoblin/tests/test_openid.py
+++ b/mediagoblin/tests/test_openid.py
@@ -19,7 +19,7 @@ import pytest
 import six
 import six.moves.urllib.parse as urlparse
 try:
-    import mock
+    from unittest import mock
 except ImportError:
     import unittest.mock as mock
 
@@ -44,14 +44,14 @@ def openid_plugin_app(request):
             'openid_appconfig.ini'))
 
 
-class TestOpenIDPlugin(object):
+class TestOpenIDPlugin:
     def _setup(self, openid_plugin_app, value=True, edit=False, delete=False):
         if value:
             response = openid_consumer.SuccessResponse(mock.Mock(), mock.Mock())
             if edit or delete:
-                response.identity_url = u'http://add.myopenid.com'
+                response.identity_url = 'http://add.myopenid.com'
             else:
-                response.identity_url = u'http://real.myopenid.com'
+                response.identity_url = 'http://real.myopenid.com'
             self._finish_verification = mock.Mock(return_value=response)
         else:
             self._finish_verification = mock.Mock(return_value=False)
@@ -113,7 +113,7 @@ class TestOpenIDPlugin(object):
             '/auth/openid/login/', {})
         context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/login.html']
         form = context['login_form']
-        assert form.openid.errors == [u'This field is required.']
+        assert form.openid.errors == ['This field is required.']
 
         # Try to login with wrong form values
         template.clear_test_template_context()
@@ -122,7 +122,7 @@ class TestOpenIDPlugin(object):
                 'openid': 'not_a_url.com'})
         context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/login.html']
         form = context['login_form']
-        assert form.openid.errors == [u'Please enter a valid url.']
+        assert form.openid.errors == ['Please enter a valid url.']
 
         # Should be no users in the db
         assert User.query.count() == 0
@@ -134,7 +134,7 @@ class TestOpenIDPlugin(object):
                 'openid': 'http://phoney.myopenid.com/'})
         context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/login.html']
         form = context['login_form']
-        assert form.openid.errors == [u'Sorry, the OpenID server could not be found']
+        assert form.openid.errors == ['Sorry, the OpenID server could not be found']
 
     def test_login(self, openid_plugin_app):
         """Tests that test login and registion with openid"""
@@ -165,7 +165,7 @@ class TestOpenIDPlugin(object):
         def _test_new_user():
             openid_plugin_app.post(
                 '/auth/openid/login/', {
-                    'openid': u'http://real.myopenid.com'})
+                    'openid': 'http://real.myopenid.com'})
 
             # Right place?
             assert 'mediagoblin/auth/register.html' in template.TEMPLATE_TEST_CONTEXT
@@ -176,8 +176,8 @@ class TestOpenIDPlugin(object):
             res = openid_plugin_app.post(
                 '/auth/openid/register/', {
                     'openid': register_form.openid.data,
-                    'username': u'chris',
-                    'email': u'chris@example.com'})
+                    'username': 'chris',
+                    'email': 'chris@example.com'})
             res.follow()
 
             # Correct place?
@@ -193,7 +193,7 @@ class TestOpenIDPlugin(object):
 
             # Get user and detach from session
             test_user = mg_globals.database.LocalUser.query.filter(
-                LocalUser.username==u'chris'
+                LocalUser.username=='chris'
             ).first()
             Session.expunge(test_user)
 
@@ -202,7 +202,7 @@ class TestOpenIDPlugin(object):
             template.clear_test_template_context()
             res = openid_plugin_app.post(
                 '/auth/openid/login/finish/', {
-                    'openid': u'http://real.myopenid.com'})
+                    'openid': 'http://real.myopenid.com'})
             res.follow()
 
             assert urlparse.urlsplit(res.location)[2] == '/'
@@ -211,7 +211,7 @@ class TestOpenIDPlugin(object):
             # Make sure user is in the session
             context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
             session = context['request'].session
-            assert session['user_id'] == six.text_type(test_user.id)
+            assert session['user_id'] == str(test_user.id)
 
         _test_new_user()
 
@@ -222,9 +222,9 @@ class TestOpenIDPlugin(object):
         context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
         register_form = context['register_form']
 
-        assert register_form.openid.errors == [u'This field is required.']
-        assert register_form.email.errors == [u'This field is required.']
-        assert register_form.username.errors == [u'This field is required.']
+        assert register_form.openid.errors == ['This field is required.']
+        assert register_form.email.errors == ['This field is required.']
+        assert register_form.username.errors == ['This field is required.']
 
         # Try to register with existing username and email
         template.clear_test_template_context()
@@ -236,14 +236,14 @@ class TestOpenIDPlugin(object):
         context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
         register_form = context['register_form']
 
-        assert register_form.username.errors == [u'Sorry, a user with that name already exists.']
-        assert register_form.email.errors == [u'Sorry, a user with that email address already exists.']
-        assert register_form.openid.errors == [u'Sorry, an account is already registered to that OpenID.']
+        assert register_form.username.errors == ['Sorry, a user with that name already exists.']
+        assert register_form.email.errors == ['Sorry, a user with that email address already exists.']
+        assert register_form.openid.errors == ['Sorry, an account is already registered to that OpenID.']
 
     def test_add_delete(self, openid_plugin_app):
         """Test adding and deleting openids"""
         # Add user
-        test_user = fixture_add_user(password='', privileges=[u'active'])
+        test_user = fixture_add_user(password='', privileges=['active'])
         openid = OpenIDUserURL()
         openid.openid_url = 'http://real.myopenid.com'
         openid.user_id = test_user.id
@@ -258,7 +258,7 @@ class TestOpenIDPlugin(object):
         def _login_user():
             openid_plugin_app.post(
                 '/auth/openid/login/finish/', {
-                    'openid': u'http://real.myopenid.com'})
+                    'openid': 'http://real.myopenid.com'})
 
         _login_user()
 
@@ -276,16 +276,16 @@ class TestOpenIDPlugin(object):
             '/edit/openid/', {})
         context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/add.html']
         form = context['form']
-        assert form.openid.errors == [u'This field is required.']
+        assert form.openid.errors == ['This field is required.']
 
         # Try with a bad url
         template.clear_test_template_context()
         openid_plugin_app.post(
             '/edit/openid/', {
-                'openid': u'not_a_url.com'})
+                'openid': 'not_a_url.com'})
         context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/add.html']
         form = context['form']
-        assert form.openid.errors == [u'Please enter a valid url.']
+        assert form.openid.errors == ['Please enter a valid url.']
 
         # Try with a url that's already registered
         template.clear_test_template_context()
@@ -294,7 +294,7 @@ class TestOpenIDPlugin(object):
                 'openid': 'http://real.myopenid.com'})
         context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/add.html']
         form = context['form']
-        assert form.openid.errors == [u'Sorry, an account is already registered to that OpenID.']
+        assert form.openid.errors == ['Sorry, an account is already registered to that OpenID.']
 
         # Test adding openid to account
         # Need to clear_test_template_context before calling _setup
@@ -303,7 +303,7 @@ class TestOpenIDPlugin(object):
 
         # Need to remove openid_url from db because it was added at setup
         openid = OpenIDUserURL.query.filter_by(
-            openid_url=u'http://add.myopenid.com')
+            openid_url='http://add.myopenid.com')
         openid.delete()
 
         @mock.patch('mediagoblin.plugins.openid.views._finish_verification', self._finish_verification)
@@ -313,7 +313,7 @@ class TestOpenIDPlugin(object):
             template.clear_test_template_context()
             res = openid_plugin_app.post(
                 '/edit/openid/', {
-                    'openid': u'http://add.myopenid.com'})
+                    'openid': 'http://add.myopenid.com'})
             res.follow()
 
             # Correct place?
@@ -322,7 +322,7 @@ class TestOpenIDPlugin(object):
 
             # OpenID Added?
             new_openid = mg_globals.database.OpenIDUserURL.query.filter_by(
-                openid_url=u'http://add.myopenid.com').first()
+                openid_url='http://add.myopenid.com').first()
             assert new_openid
 
         _test_add()
@@ -357,14 +357,14 @@ class TestOpenIDPlugin(object):
                     'openid': 'http://realfake.myopenid.com/'})
             context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/delete.html']
             form = context['form']
-            assert form.openid.errors == [u'That OpenID is not registered to this account.']
+            assert form.openid.errors == ['That OpenID is not registered to this account.']
 
             # Delete OpenID
             # Kind of weird to POST to delete/finish
             template.clear_test_template_context()
             res = openid_plugin_app.post(
                 '/edit/openid/delete/finish/', {
-                    'openid': u'http://add.myopenid.com'})
+                    'openid': 'http://add.myopenid.com'})
             res.follow()
 
             # Correct place?
@@ -373,7 +373,7 @@ class TestOpenIDPlugin(object):
 
             # OpenID deleted?
             new_openid = mg_globals.database.OpenIDUserURL.query.filter_by(
-                openid_url=u'http://add.myopenid.com').first()
+                openid_url='http://add.myopenid.com').first()
             assert not new_openid
 
         _test_delete(self, test_user)
diff --git a/mediagoblin/tests/test_persona.py b/mediagoblin/tests/test_persona.py
index 437cb7a1..25f0ca1e 100644
--- a/mediagoblin/tests/test_persona.py
+++ b/mediagoblin/tests/test_persona.py
@@ -18,7 +18,7 @@ import pkg_resources
 import pytest
 import six
 try:
-    import mock
+    from unittest import mock
 except ImportError:
     import unittest.mock as mock
 
@@ -43,7 +43,7 @@ def persona_plugin_app(request):
             'persona_appconfig.ini'))
 
 
-class TestPersonaPlugin(object):
+class TestPersonaPlugin:
     def test_authentication_views(self, persona_plugin_app):
         res = persona_plugin_app.get('/auth/login/')
 
@@ -61,7 +61,7 @@ class TestPersonaPlugin(object):
 
         assert urlparse.urlsplit(res.location)[2] == '/auth/login/'
 
-        @mock.patch('mediagoblin.plugins.persona.views._get_response', mock.Mock(return_value=u'test@example.com'))
+        @mock.patch('mediagoblin.plugins.persona.views._get_response', mock.Mock(return_value='test@example.com'))
         def _test_registration():
             # No register users
             template.clear_test_template_context()
@@ -72,8 +72,8 @@ class TestPersonaPlugin(object):
             context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
             register_form = context['register_form']
 
-            assert register_form.email.data == u'test@example.com'
-            assert register_form.persona_email.data == u'test@example.com'
+            assert register_form.email.data == 'test@example.com'
+            assert register_form.persona_email.data == 'test@example.com'
 
             template.clear_test_template_context()
             res = persona_plugin_app.post(
@@ -83,9 +83,9 @@ class TestPersonaPlugin(object):
             context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
             register_form = context['register_form']
 
-            assert register_form.username.errors == [u'This field is required.']
-            assert register_form.email.errors == [u'This field is required.']
-            assert register_form.persona_email.errors == [u'This field is required.']
+            assert register_form.username.errors == ['This field is required.']
+            assert register_form.email.errors == ['This field is required.']
+            assert register_form.persona_email.errors == ['This field is required.']
 
             # Successful register
             template.clear_test_template_context()
@@ -111,21 +111,21 @@ class TestPersonaPlugin(object):
             context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
             register_form = context['register_form']
 
-            assert register_form.persona_email.errors == [u'Sorry, an account is already registered to that Persona email.']
+            assert register_form.persona_email.errors == ['Sorry, an account is already registered to that Persona email.']
 
             # Logout
             persona_plugin_app.get('/auth/logout/')
 
             # Get user and detach from session
             test_user = mg_globals.database.LocalUser.query.filter(
-                LocalUser.username==u'chris'
+                LocalUser.username=='chris'
             ).first()
             active_privilege = Privilege.query.filter(
-                Privilege.privilege_name==u'active').first()
+                Privilege.privilege_name=='active').first()
             test_user.all_privileges.append(active_privilege)
             test_user.save()
             test_user = mg_globals.database.LocalUser.query.filter(
-                LocalUser.username==u'chris'
+                LocalUser.username=='chris'
             ).first()
             Session.expunge(test_user)
 
@@ -148,11 +148,11 @@ class TestPersonaPlugin(object):
             # Make sure user is in the session
             context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
             session = context['request'].session
-            assert session['user_id'] == six.text_type(test_user.id)
+            assert session['user_id'] == str(test_user.id)
 
         _test_registration()
 
-        @mock.patch('mediagoblin.plugins.persona.views._get_response', mock.Mock(return_value=u'new@example.com'))
+        @mock.patch('mediagoblin.plugins.persona.views._get_response', mock.Mock(return_value='new@example.com'))
         def _test_edit_persona():
             # Try and delete only Persona email address
             template.clear_test_template_context()
@@ -164,7 +164,7 @@ class TestPersonaPlugin(object):
             context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/persona/edit.html']
             form = context['form']
 
-            assert form.email.errors == [u"You can't delete your only Persona email address unless you have a password set."]
+            assert form.email.errors == ["You can't delete your only Persona email address unless you have a password set."]
 
             template.clear_test_template_context()
             res = persona_plugin_app.post(
@@ -174,7 +174,7 @@ class TestPersonaPlugin(object):
             context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/persona/edit.html']
             form = context['form']
 
-            assert form.email.errors == [u'This field is required.']
+            assert form.email.errors == ['This field is required.']
 
             # Try and delete Persona not owned by the user
             template.clear_test_template_context()
@@ -186,7 +186,7 @@ class TestPersonaPlugin(object):
             context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/persona/edit.html']
             form = context['form']
 
-            assert form.email.errors == [u'That Persona email address is not registered to this account.']
+            assert form.email.errors == ['That Persona email address is not registered to this account.']
 
             res = persona_plugin_app.get('/edit/persona/add/')
 
@@ -210,7 +210,7 @@ class TestPersonaPlugin(object):
 
         _test_edit_persona()
 
-        @mock.patch('mediagoblin.plugins.persona.views._get_response', mock.Mock(return_value=u'test1@example.com'))
+        @mock.patch('mediagoblin.plugins.persona.views._get_response', mock.Mock(return_value='test1@example.com'))
         def _test_add_existing():
             template.clear_test_template_context()
             res = persona_plugin_app.post(
diff --git a/mediagoblin/tests/test_piwigo.py b/mediagoblin/tests/test_piwigo.py
index 33aea580..c530d457 100644
--- a/mediagoblin/tests/test_piwigo.py
+++ b/mediagoblin/tests/test_piwigo.py
@@ -21,14 +21,14 @@ from .tools import fixture_add_user
 XML_PREFIX = "\n"
 
 
-class Test_PWG(object):
+class Test_PWG:
     @pytest.fixture(autouse=True)
     def setup(self, test_app):
         self.test_app = test_app
 
         fixture_add_user()
 
-        self.username = u"chris"
+        self.username = "chris"
         self.password = "toast"
 
     def do_post(self, method, params):
@@ -43,7 +43,7 @@ class Test_PWG(object):
 
     def test_session(self):
         resp = self.do_post("pwg.session.login",
-            {"username": u"nouser", "password": "wrong"})
+            {"username": "nouser", "password": "wrong"})
         assert resp.body == (XML_PREFIX + '').encode('ascii')
 
         resp = self.do_post("pwg.session.login",
diff --git a/mediagoblin/tests/test_pluginapi.py b/mediagoblin/tests/test_pluginapi.py
index 2fd6df39..4762f3f8 100644
--- a/mediagoblin/tests/test_pluginapi.py
+++ b/mediagoblin/tests/test_pluginapi.py
@@ -402,7 +402,7 @@ def test_plugin_assetlink(static_plugin_app):
     # link dir doesn't exist, link it
     result = run_assetlink().collection[0]
     assert result == \
-        'Linked asset directory for plugin "staticstuff":\n  %s\nto:\n  %s\n' % (
+        'Linked asset directory for plugin "staticstuff":\n  {}\nto:\n  {}\n'.format(
             plugin_static.file_path.rstrip(os.path.sep),
             plugin_link_dir)
     assert os.path.lexists(plugin_link_dir)
@@ -430,10 +430,10 @@ def test_plugin_assetlink(static_plugin_app):
     result = run_assetlink().combined_string
     assert result == """Old link found for "staticstuff"; removing.
 Linked asset directory for plugin "staticstuff":
-  %s
+  {}
 to:
-  %s
-""" % (plugin_static.file_path.rstrip(os.path.sep), plugin_link_dir)
+  {}
+""".format(plugin_static.file_path.rstrip(os.path.sep), plugin_link_dir)
     assert os.path.lexists(plugin_link_dir)
     assert os.path.islink(plugin_link_dir)
     assert os.path.realpath(plugin_link_dir) == plugin_static.file_path
@@ -447,7 +447,7 @@ to:
     assert result == 'Could not link "staticstuff": %s exists and is not a symlink\n' % (
         plugin_link_dir)
 
-    with open(plugin_link_dir, 'r') as clobber_file:
+    with open(plugin_link_dir) as clobber_file:
         assert clobber_file.read() == 'clobbered!'
 
 
diff --git a/mediagoblin/tests/test_privileges.py b/mediagoblin/tests/test_privileges.py
index 2e0b7347..b7a9ff3b 100644
--- a/mediagoblin/tests/test_privileges.py
+++ b/mediagoblin/tests/test_privileges.py
@@ -32,12 +32,12 @@ class TestPrivilegeFunctionality:
     def _setup(self, test_app):
         self.test_app = test_app
 
-        fixture_add_user(u'alex',
-            privileges=[u'admin',u'active'])
-        fixture_add_user(u'meow',
-            privileges=[u'moderator',u'active',u'reporter'])
-        fixture_add_user(u'natalie',
-            privileges=[u'active'])
+        fixture_add_user('alex',
+            privileges=['admin','active'])
+        fixture_add_user('meow',
+            privileges=['moderator','active','reporter'])
+        fixture_add_user('natalie',
+            privileges=['active'])
         self.query_for_users()
 
     def login(self, username):
@@ -64,17 +64,17 @@ class TestPrivilegeFunctionality:
         return response, context_data
 
     def query_for_users(self):
-        self.admin_user = LocalUser.query.filter(LocalUser.username==u'alex').first()
-        self.mod_user = LocalUser.query.filter(LocalUser.username==u'meow').first()
-        self.user = LocalUser.query.filter(LocalUser.username==u'natalie').first()
+        self.admin_user = LocalUser.query.filter(LocalUser.username=='alex').first()
+        self.mod_user = LocalUser.query.filter(LocalUser.username=='meow').first()
+        self.user = LocalUser.query.filter(LocalUser.username=='natalie').first()
 
     def testUserBanned(self):
-        self.login(u'natalie')
+        self.login('natalie')
         uid = self.user.id
         # First, test what happens when a user is banned indefinitely
         #----------------------------------------------------------------------
         user_ban = UserBan(user_id=uid,
-            reason=u'Testing whether user is banned',
+            reason='Testing whether user is banned',
             expiration_date=None)
         user_ban.save()
 
@@ -87,7 +87,7 @@ class TestPrivilegeFunctionality:
         user_ban = UserBan.query.get(uid)
         user_ban.delete()
         user_ban = UserBan(user_id=uid,
-            reason=u'Testing whether user is banned',
+            reason='Testing whether user is banned',
             expiration_date= date.today() + timedelta(days=20))
         user_ban.save()
 
@@ -102,7 +102,7 @@ class TestPrivilegeFunctionality:
         user_ban.delete()
         exp_date = date.today() - timedelta(days=20)
         user_ban = UserBan(user_id=uid,
-            reason=u'Testing whether user is banned',
+            reason='Testing whether user is banned',
             expiration_date= exp_date)
         user_ban.save()
 
@@ -122,7 +122,7 @@ class TestPrivilegeFunctionality:
         #       tests/test_reporting.py                 reporter
         #       tests/test_submission.py                uploader
         #----------------------------------------------------------------------
-        self.login(u'natalie')
+        self.login('natalie')
 
         # First test the get and post requests of submission/uploading
         #----------------------------------------------------------------------
@@ -134,7 +134,7 @@ class TestPrivilegeFunctionality:
 
         with pytest.raises(AppError) as excinfo:
             response = self.do_post({'upload_files':[('file',GOOD_JPG)],
-                'title':u'Normal Upload 1'},
+                'title':'Normal Upload 1'},
                 url='/submit/')
         excinfo = str(excinfo) if six.PY2 else str(excinfo).encode('ascii')
         assert b'Bad response: 403 FORBIDDEN' in excinfo
@@ -144,12 +144,12 @@ class TestPrivilegeFunctionality:
         self.query_for_users()
 
         media_entry = fixture_media_entry(uploader=self.admin_user.id,
-            state=u'processed')
+            state='processed')
 
         media_entry_id = media_entry.id
-        media_uri_id = '/u/{0}/m/{1}/'.format(self.admin_user.username,
+        media_uri_id = '/u/{}/m/{}/'.format(self.admin_user.username,
                                               media_entry.id)
-        media_uri_slug = '/u/{0}/m/{1}/'.format(self.admin_user.username,
+        media_uri_slug = '/u/{}/m/{}/'.format(self.admin_user.username,
                                                 media_entry.slug)
         response = self.test_app.get(media_uri_slug)
         assert not b"Add a comment" in response.body
@@ -158,7 +158,7 @@ class TestPrivilegeFunctionality:
         with pytest.raises(AppError) as excinfo:
             response = self.test_app.post(
                 media_uri_id + 'comment/add/',
-                {'comment_content': u'Test comment #42'})
+                {'comment_content': 'Test comment #42'})
         excinfo = str(excinfo) if six.PY2 else str(excinfo).encode('ascii')
         assert b'Bad response: 403 FORBIDDEN' in excinfo
 
@@ -171,8 +171,8 @@ class TestPrivilegeFunctionality:
 
         with pytest.raises(AppError) as excinfo:
             response = self.do_post(
-                {'report_reason':u'Testing Reports #1',
-                'reporter_id':u'3'},
+                {'report_reason':'Testing Reports #1',
+                'reporter_id':'3'},
                 url=(media_uri_slug+"report/"))
         excinfo = str(excinfo) if six.PY2 else str(excinfo).encode('ascii')
         assert b'Bad response: 403 FORBIDDEN' in excinfo
@@ -208,8 +208,8 @@ class TestPrivilegeFunctionality:
         self.query_for_users()
 
         with pytest.raises(AppError) as excinfo:
-            response, context = self.do_post({'action_to_resolve':[u'takeaway'],
-                'take_away_privileges':[u'active'],
+            response, context = self.do_post({'action_to_resolve':['takeaway'],
+                'take_away_privileges':['active'],
                 'targeted_user':self.admin_user.id},
                 url='/mod/reports/1/')
             self.query_for_users()
diff --git a/mediagoblin/tests/test_processing.py b/mediagoblin/tests/test_processing.py
index 591add96..ffbdbbc2 100644
--- a/mediagoblin/tests/test_processing.py
+++ b/mediagoblin/tests/test_processing.py
@@ -2,7 +2,7 @@
 
 from mediagoblin import processing
 
-class TestProcessing(object):
+class TestProcessing:
     def run_fill(self, input, format, output=None):
         builder = processing.FilenameBuilder(input)
         result = builder.fill(format)
@@ -14,5 +14,5 @@ class TestProcessing(object):
         self.run_fill('/home/user/foo.TXT', '{basename}bar{ext}', 'foobar.txt')
 
     def test_long_filename_fill(self):
-        self.run_fill('{0}.png'.format('A' * 300), 'image-{basename}{ext}',
-                      'image-{0}.png'.format('A' * 245))
+        self.run_fill('{}.png'.format('A' * 300), 'image-{basename}{ext}',
+                      'image-{}.png'.format('A' * 245))
diff --git a/mediagoblin/tests/test_reporting.py b/mediagoblin/tests/test_reporting.py
index 803fc849..a59ea28e 100644
--- a/mediagoblin/tests/test_reporting.py
+++ b/mediagoblin/tests/test_reporting.py
@@ -28,10 +28,10 @@ class TestReportFiling:
     def _setup(self, test_app):
         self.test_app = test_app
 
-        fixture_add_user(u'allie',
-            privileges=[u'reporter',u'active'])
-        fixture_add_user(u'natalie',
-            privileges=[u'active', u'moderator'])
+        fixture_add_user('allie',
+            privileges=['reporter','active'])
+        fixture_add_user('natalie',
+            privileges=['active', 'moderator'])
 
     def login(self, username):
         self.test_app.post(
@@ -55,27 +55,27 @@ class TestReportFiling:
         return response, context_data
 
     def query_for_users(self):
-        return (LocalUser.query.filter(LocalUser.username==u'allie').first(),
-        LocalUser.query.filter(LocalUser.username==u'natalie').first())
+        return (LocalUser.query.filter(LocalUser.username=='allie').first(),
+        LocalUser.query.filter(LocalUser.username=='natalie').first())
 
     def testMediaReports(self):
-        self.login(u'allie')
+        self.login('allie')
         allie_user, natalie_user = self.query_for_users()
         allie_id = allie_user.id
 
         media_entry = fixture_media_entry(uploader=natalie_user.id,
-            state=u'processed')
+            state='processed')
 
         mid = media_entry.id
-        media_uri_slug = '/u/{0}/m/{1}/'.format(natalie_user.username,
+        media_uri_slug = '/u/{}/m/{}/'.format(natalie_user.username,
                                                 media_entry.slug)
 
         response = self.test_app.get(media_uri_slug + "report/")
         assert response.status == "200 OK"
 
         response, context = self.do_post(
-            {'report_reason':u'Testing Media Report',
-            'reporter_id':six.text_type(allie_id)},url= media_uri_slug + "report/")
+            {'report_reason':'Testing Media Report',
+            'reporter_id':str(allie_id)},url= media_uri_slug + "report/")
 
         assert response.status == "302 FOUND"
 
@@ -83,18 +83,18 @@ class TestReportFiling:
 
         allie_user, natalie_user = self.query_for_users()
         assert media_report is not None
-        assert media_report.report_content == u'Testing Media Report'
+        assert media_report.report_content == 'Testing Media Report'
         assert media_report.reporter_id == allie_id
         assert media_report.reported_user_id == natalie_user.id
         assert media_report.created is not None
 
     def testCommentReports(self):
-        self.login(u'allie')
+        self.login('allie')
         allie_user, natalie_user = self.query_for_users()
         allie_id = allie_user.id
 
         media_entry = fixture_media_entry(uploader=natalie_user.id,
-            state=u'processed')
+            state='processed')
         mid = media_entry.id
         fixture_add_comment(
             media_entry=media_entry,
@@ -102,7 +102,7 @@ class TestReportFiling:
         )
         comment = TextComment.query.first()
 
-        comment_uri_slug = '/u/{0}/m/{1}/c/{2}/'.format(natalie_user.username,
+        comment_uri_slug = '/u/{}/m/{}/c/{}/'.format(natalie_user.username,
                                                 media_entry.slug,
                                                 comment.id)
 
@@ -110,8 +110,8 @@ class TestReportFiling:
         assert response.status == "200 OK"
 
         response, context = self.do_post({
-            'report_reason':u'Testing Comment Report',
-            'reporter_id':six.text_type(allie_id)},url= comment_uri_slug + "report/")
+            'report_reason':'Testing Comment Report',
+            'reporter_id':str(allie_id)},url= comment_uri_slug + "report/")
 
         assert response.status == "302 FOUND"
 
@@ -119,33 +119,33 @@ class TestReportFiling:
 
         allie_user, natalie_user = self.query_for_users()
         assert comment_report is not None
-        assert comment_report.report_content == u'Testing Comment Report'
+        assert comment_report.report_content == 'Testing Comment Report'
         assert comment_report.reporter_id == allie_id
         assert comment_report.reported_user_id == natalie_user.id
         assert comment_report.created is not None
 
     def testArchivingReports(self):
-        self.login(u'natalie')
+        self.login('natalie')
         allie_user, natalie_user = self.query_for_users()
         allie_id, natalie_id = allie_user.id, natalie_user.id
 
         fixture_add_comment(author=allie_user.id,
-            comment=u'Comment will be removed')
+            comment='Comment will be removed')
         test_comment = TextComment.query.filter(
             TextComment.actor==allie_user.id).first()
         fixture_add_comment_report(comment=test_comment,
             reported_user=allie_user,
-            report_content=u'Testing Archived Reports #1',
+            report_content='Testing Archived Reports #1',
             reporter=natalie_user)
         comment_report = Report.query.filter(
             Report.reported_user==allie_user).first()
 
-        assert comment_report.report_content == u'Testing Archived Reports #1'
+        assert comment_report.report_content == 'Testing Archived Reports #1'
         response, context = self.do_post(
-            {'action_to_resolve':[u'userban', u'delete'],
+            {'action_to_resolve':['userban', 'delete'],
             'targeted_user':allie_user.id,
-            'resolution_content':u'This is a test of archiving reports.'},
-            url='/mod/reports/{0}/'.format(comment_report.id))
+            'resolution_content':'This is a test of archiving reports.'},
+            url='/mod/reports/{}/'.format(comment_report.id))
 
         assert response.status == "302 FOUND"
         allie_user, natalie_user = self.query_for_users()
@@ -155,11 +155,11 @@ class TestReportFiling:
 
         assert Report.query.count() != 0
         assert archived_report is not None
-        assert archived_report.report_content == u'Testing Archived Reports #1'
+        assert archived_report.report_content == 'Testing Archived Reports #1'
         assert archived_report.reporter_id == natalie_id
         assert archived_report.reported_user_id == allie_id
         assert archived_report.created is not None
         assert archived_report.resolved is not None
-        assert archived_report.result == u'''This is a test of archiving reports.
+        assert archived_report.result == '''This is a test of archiving reports.
 natalie banned user allie indefinitely.
 natalie deleted the comment.'''
diff --git a/mediagoblin/tests/test_response.py b/mediagoblin/tests/test_response.py
index 7f929155..27a69d72 100644
--- a/mediagoblin/tests/test_response.py
+++ b/mediagoblin/tests/test_response.py
@@ -14,13 +14,12 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see .
 
-from __future__ import absolute_import, unicode_literals
 
 from werkzeug.wrappers import Request
 
 from ..tools.response import redirect, redirect_obj
 
-class TestRedirect(object):
+class TestRedirect:
     def test_redirect_respects_location(self):
         """Test that redirect returns a 302 to location specified."""
         request = Request({})
@@ -54,7 +53,7 @@ class TestRedirect(object):
 
         # Using a mock obj here so that we're only testing redirect_obj itself,
         # rather than also testing the url_for_self implementation.
-        class Foo(object):
+        class Foo:
             def url_for_self(*args, **kwargs):
                 return '/foo'
 
diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py
index ef6a9f5b..9bc9d8c4 100644
--- a/mediagoblin/tests/test_sql_migrations.py
+++ b/mediagoblin/tests/test_sql_migrations.py
@@ -195,7 +195,7 @@ def level_exits_new_table(db_conn):
 
     for level in result:
 
-        for exit_name, to_level in six.iteritems(level['exits']):
+        for exit_name, to_level in level['exits'].items():
             # Insert the level exit
             db_conn.execute(
                 level_exits.insert().values(
@@ -270,7 +270,7 @@ def creature_num_legs_to_num_limbs(db_conn):
     creature_table = Table(
         'creature', metadata,
         autoload=True, autoload_with=db_conn.bind)
-    creature_table.c.num_legs.alter(name=u"num_limbs")
+    creature_table.c.num_legs.alter(name="num_limbs")
 
 
 @RegisterMigration(5, FULL_MIGRATIONS)
@@ -359,34 +359,34 @@ def _insert_migration1_objects(session):
     """
     # Insert creatures
     session.add_all(
-        [Creature1(name=u'centipede',
+        [Creature1(name='centipede',
                    num_legs=100,
                    is_demon=False),
-         Creature1(name=u'wolf',
+         Creature1(name='wolf',
                    num_legs=4,
                    is_demon=False),
          # don't ask me what a wizardsnake is.
-         Creature1(name=u'wizardsnake',
+         Creature1(name='wizardsnake',
                    num_legs=0,
                    is_demon=True)])
 
     # Insert levels
     session.add_all(
-        [Level1(id=u'necroplex',
-                name=u'The Necroplex',
-                description=u'A complex full of pure deathzone.',
+        [Level1(id='necroplex',
+                name='The Necroplex',
+                description='A complex full of pure deathzone.',
                 exits={
-                    u'deathwell': u'evilstorm',
-                    u'portal': u'central_park'}),
-         Level1(id=u'evilstorm',
-                name=u'Evil Storm',
-                description=u'A storm full of pure evil.',
+                    'deathwell': 'evilstorm',
+                    'portal': 'central_park'}),
+         Level1(id='evilstorm',
+                name='Evil Storm',
+                description='A storm full of pure evil.',
                 exits={}), # you can't escape the evilstorm
-         Level1(id=u'central_park',
-                name=u'Central Park, NY, NY',
-                description=u"New York's friendly Central Park.",
+         Level1(id='central_park',
+                name='Central Park, NY, NY',
+                description="New York's friendly Central Park.",
                 exits={
-                    u'portal': u'necroplex'})])
+                    'portal': 'necroplex'})])
 
     session.commit()
 
@@ -398,71 +398,71 @@ def _insert_migration2_objects(session):
     # Insert creatures
     session.add_all(
         [Creature2(
-                name=u'centipede',
+                name='centipede',
                 num_legs=100),
          Creature2(
-                name=u'wolf',
+                name='wolf',
                 num_legs=4,
                 magical_powers = [
                     CreaturePower2(
-                        name=u"ice breath",
-                        description=u"A blast of icy breath!",
+                        name="ice breath",
+                        description="A blast of icy breath!",
                         hitpower=20),
                     CreaturePower2(
-                        name=u"death stare",
-                        description=u"A frightening stare, for sure!",
+                        name="death stare",
+                        description="A frightening stare, for sure!",
                         hitpower=45)]),
          Creature2(
-                name=u'wizardsnake',
+                name='wizardsnake',
                 num_legs=0,
                 magical_powers=[
                     CreaturePower2(
-                        name=u'death_rattle',
-                        description=u'A rattle... of DEATH!',
+                        name='death_rattle',
+                        description='A rattle... of DEATH!',
                         hitpower=1000),
                     CreaturePower2(
-                        name=u'sneaky_stare',
-                        description=u"The sneakiest stare you've ever seen!",
+                        name='sneaky_stare',
+                        description="The sneakiest stare you've ever seen!",
                         hitpower=300),
                     CreaturePower2(
-                        name=u'slithery_smoke',
-                        description=u"A blast of slithery, slithery smoke.",
+                        name='slithery_smoke',
+                        description="A blast of slithery, slithery smoke.",
                         hitpower=10),
                     CreaturePower2(
-                        name=u'treacherous_tremors',
-                        description=u"The ground shakes beneath footed animals!",
+                        name='treacherous_tremors',
+                        description="The ground shakes beneath footed animals!",
                         hitpower=0)])])
 
     # Insert levels
     session.add_all(
-        [Level2(id=u'necroplex',
-                name=u'The Necroplex',
-                description=u'A complex full of pure deathzone.'),
-         Level2(id=u'evilstorm',
-                name=u'Evil Storm',
-                description=u'A storm full of pure evil.',
+        [Level2(id='necroplex',
+                name='The Necroplex',
+                description='A complex full of pure deathzone.'),
+         Level2(id='evilstorm',
+                name='Evil Storm',
+                description='A storm full of pure evil.',
                 exits=[]), # you can't escape the evilstorm
-         Level2(id=u'central_park',
-                name=u'Central Park, NY, NY',
-                description=u"New York's friendly Central Park.")])
+         Level2(id='central_park',
+                name='Central Park, NY, NY',
+                description="New York's friendly Central Park.")])
 
     # necroplex exits
     session.add_all(
-        [LevelExit2(name=u'deathwell',
-                    from_level=u'necroplex',
-                    to_level=u'evilstorm'),
-         LevelExit2(name=u'portal',
-                    from_level=u'necroplex',
-                    to_level=u'central_park')])
+        [LevelExit2(name='deathwell',
+                    from_level='necroplex',
+                    to_level='evilstorm'),
+         LevelExit2(name='portal',
+                    from_level='necroplex',
+                    to_level='central_park')])
 
     # there are no evilstorm exits because there is no exit from the
     # evilstorm
       
     # central park exits
     session.add_all(
-        [LevelExit2(name=u'portal',
-                    from_level=u'central_park',
-                    to_level=u'necroplex')])
+        [LevelExit2(name='portal',
+                    from_level='central_park',
+                    to_level='necroplex')])
 
     session.commit()
 
@@ -474,80 +474,80 @@ def _insert_migration3_objects(session):
     # Insert creatures
     session.add_all(
         [Creature3(
-                name=u'centipede',
+                name='centipede',
                 num_limbs=100),
          Creature3(
-                name=u'wolf',
+                name='wolf',
                 num_limbs=4,
                 magical_powers = [
                     CreaturePower3(
-                        name=u"ice breath",
-                        description=u"A blast of icy breath!",
+                        name="ice breath",
+                        description="A blast of icy breath!",
                         hitpower=20.0),
                     CreaturePower3(
-                        name=u"death stare",
-                        description=u"A frightening stare, for sure!",
+                        name="death stare",
+                        description="A frightening stare, for sure!",
                         hitpower=45.0)]),
          Creature3(
-                name=u'wizardsnake',
+                name='wizardsnake',
                 num_limbs=0,
                 magical_powers=[
                     CreaturePower3(
-                        name=u'death_rattle',
-                        description=u'A rattle... of DEATH!',
+                        name='death_rattle',
+                        description='A rattle... of DEATH!',
                         hitpower=1000.0),
                     CreaturePower3(
-                        name=u'sneaky_stare',
-                        description=u"The sneakiest stare you've ever seen!",
+                        name='sneaky_stare',
+                        description="The sneakiest stare you've ever seen!",
                         hitpower=300.0),
                     CreaturePower3(
-                        name=u'slithery_smoke',
-                        description=u"A blast of slithery, slithery smoke.",
+                        name='slithery_smoke',
+                        description="A blast of slithery, slithery smoke.",
                         hitpower=10.0),
                     CreaturePower3(
-                        name=u'treacherous_tremors',
-                        description=u"The ground shakes beneath footed animals!",
+                        name='treacherous_tremors',
+                        description="The ground shakes beneath footed animals!",
                         hitpower=0.0)])],
         # annnnnd one more to test a floating point hitpower
         Creature3(
-                name=u'deity',
+                name='deity',
                 numb_limbs=30,
                 magical_powers=[
                     CreaturePower3(
-                        name=u'smite',
-                        description=u'Smitten by holy wrath!',
+                        name='smite',
+                        description='Smitten by holy wrath!',
                         hitpower=9999.9)]))
 
     # Insert levels
     session.add_all(
-        [Level3(id=u'necroplex',
-                name=u'The Necroplex',
-                description=u'A complex full of pure deathzone.'),
-         Level3(id=u'evilstorm',
-                name=u'Evil Storm',
-                description=u'A storm full of pure evil.',
+        [Level3(id='necroplex',
+                name='The Necroplex',
+                description='A complex full of pure deathzone.'),
+         Level3(id='evilstorm',
+                name='Evil Storm',
+                description='A storm full of pure evil.',
                 exits=[]), # you can't escape the evilstorm
-         Level3(id=u'central_park',
-                name=u'Central Park, NY, NY',
-                description=u"New York's friendly Central Park.")])
+         Level3(id='central_park',
+                name='Central Park, NY, NY',
+                description="New York's friendly Central Park.")])
 
     # necroplex exits
     session.add_all(
-        [LevelExit3(name=u'deathwell',
-                    from_level=u'necroplex',
-                    to_level=u'evilstorm'),
-         LevelExit3(name=u'portal',
-                    from_level=u'necroplex',
-                    to_level=u'central_park')])
+        [LevelExit3(name='deathwell',
+                    from_level='necroplex',
+                    to_level='evilstorm'),
+         LevelExit3(name='portal',
+                    from_level='necroplex',
+                    to_level='central_park')])
 
     # there are no evilstorm exits because there is no exit from the
     # evilstorm
       
     # central park exits
     session.add_all(
-        [LevelExit3(name=u'portal',
-                    from_level=u'central_park',
-                    to_level=u'necroplex')])
+        [LevelExit3(name='portal',
+                    from_level='central_park',
+                    to_level='necroplex')])
 
     session.commit()
 
@@ -563,10 +563,10 @@ def assert_col_type(column, this_class):
 
 
 def _get_level3_exits(session, level):
-    return dict(
-        [(level_exit.name, level_exit.to_level)
+    return {
+        level_exit.name: level_exit.to_level
          for level_exit in
-         session.query(LevelExit3).filter_by(from_level=level.id)])
+         session.query(LevelExit3).filter_by(from_level=level.id)}
 
 
 @pytest.mark.skipif(six.PY2, reason='Breaks in Python 2 but migrations seem to run ok')
@@ -581,7 +581,7 @@ def test_set1_to_set3():
 
     printer = CollectingPrinter()
     migration_manager = MigrationManager(
-        u'__main__', SET1_MODELS, SET1_MIGRATIONS, Session(),
+        '__main__', SET1_MODELS, SET1_MIGRATIONS, Session(),
         printer)
 
     # Check latest migration and database current migration
@@ -591,7 +591,7 @@ def test_set1_to_set3():
     result = migration_manager.init_or_migrate()
 
     # Make sure output was "inited"
-    assert result == u'inited'
+    assert result == 'inited'
     # Check output
     assert printer.combined_string == (
         "-> Initializing main mediagoblin tables... done.\n")
@@ -607,7 +607,7 @@ def test_set1_to_set3():
 
     # Try to "re-migrate" with same manager settings... nothing should happen
     migration_manager = MigrationManager(
-        u'__main__', SET1_MODELS, SET1_MIGRATIONS, 
+        '__main__', SET1_MODELS, SET1_MIGRATIONS, 
         Session(), printer)
     assert migration_manager.init_or_migrate() == None
 
@@ -622,8 +622,8 @@ def test_set1_to_set3():
     creature_table = Table(
         'creature', metadata,
         autoload=True, autoload_with=engine)
-    assert set(creature_table.c.keys()) == set(
-        ['id', 'name', 'num_legs', 'is_demon'])
+    assert set(creature_table.c.keys()) == {
+        'id', 'name', 'num_legs', 'is_demon'}
     assert_col_type(creature_table.c.id, Integer)
     assert_col_type(creature_table.c.name, VARCHAR)
     assert creature_table.c.name.nullable is False
@@ -637,8 +637,8 @@ def test_set1_to_set3():
     level_table = Table(
         'level', metadata,
         autoload=True, autoload_with=engine)
-    assert set(level_table.c.keys()) == set(
-        ['id', 'name', 'description', 'exits'])
+    assert set(level_table.c.keys()) == {
+        'id', 'name', 'description', 'exits'}
     assert_col_type(level_table.c.id, VARCHAR)
     assert level_table.c.id.primary_key is True
     assert_col_type(level_table.c.name, VARCHAR)
@@ -652,38 +652,38 @@ def test_set1_to_set3():
     # Check the creation of the inserted rows on the creature and levels tables
   
     creature = session.query(Creature1).filter_by(
-        name=u'centipede').one()
+        name='centipede').one()
     assert creature.num_legs == 100
     assert creature.is_demon == False
 
     creature = session.query(Creature1).filter_by(
-        name=u'wolf').one()
+        name='wolf').one()
     assert creature.num_legs == 4
     assert creature.is_demon == False
 
     creature = session.query(Creature1).filter_by(
-        name=u'wizardsnake').one()
+        name='wizardsnake').one()
     assert creature.num_legs == 0
     assert creature.is_demon == True
 
     level = session.query(Level1).filter_by(
-        id=u'necroplex').one()
-    assert level.name == u'The Necroplex'
-    assert level.description == u'A complex full of pure deathzone.'
+        id='necroplex').one()
+    assert level.name == 'The Necroplex'
+    assert level.description == 'A complex full of pure deathzone.'
     assert level.exits == {
         'deathwell': 'evilstorm',
         'portal': 'central_park'}
 
     level = session.query(Level1).filter_by(
-        id=u'evilstorm').one()
-    assert level.name == u'Evil Storm'
-    assert level.description == u'A storm full of pure evil.'
+        id='evilstorm').one()
+    assert level.name == 'Evil Storm'
+    assert level.description == 'A storm full of pure evil.'
     assert level.exits == {}  # You still can't escape the evilstorm!
 
     level = session.query(Level1).filter_by(
-        id=u'central_park').one()
-    assert level.name == u'Central Park, NY, NY'
-    assert level.description == u"New York's friendly Central Park."
+        id='central_park').one()
+    assert level.name == 'Central Park, NY, NY'
+    assert level.description == "New York's friendly Central Park."
     assert level.exits == {
         'portal': 'necroplex'}
 
@@ -691,7 +691,7 @@ def test_set1_to_set3():
     # isn't said to be updated yet
     printer = CollectingPrinter()
     migration_manager = MigrationManager(
-        u'__main__', SET3_MODELS, SET3_MIGRATIONS, Session(),
+        '__main__', SET3_MODELS, SET3_MIGRATIONS, Session(),
         printer)
 
     assert migration_manager.latest_migration == 8
@@ -701,7 +701,7 @@ def test_set1_to_set3():
     result = migration_manager.init_or_migrate()
 
     # Make sure result was "migrated"
-    assert result == u'migrated'
+    assert result == 'migrated'
 
     # TODO: Check output to user
     assert printer.combined_string == """\
@@ -718,7 +718,7 @@ def test_set1_to_set3():
     
     # Make sure version matches expected
     migration_manager = MigrationManager(
-        u'__main__', SET3_MODELS, SET3_MIGRATIONS, Session(),
+        '__main__', SET3_MODELS, SET3_MIGRATIONS, Session(),
         printer)
     assert migration_manager.latest_migration == 8
     assert migration_manager.database_current_migration == 8
@@ -732,8 +732,8 @@ def test_set1_to_set3():
         autoload=True, autoload_with=engine)
     # assert set(creature_table.c.keys()) == set(
     #     ['id', 'name', 'num_limbs'])
-    assert set(creature_table.c.keys()) == set(
-        [u'id', 'name', u'num_limbs', u'is_demon'])
+    assert set(creature_table.c.keys()) == {
+        'id', 'name', 'num_limbs', 'is_demon'}
     assert_col_type(creature_table.c.id, Integer)
     assert_col_type(creature_table.c.name, VARCHAR)
     assert creature_table.c.name.nullable is False
@@ -746,8 +746,8 @@ def test_set1_to_set3():
     creature_power_table = Table(
         'creature_power', metadata,
         autoload=True, autoload_with=engine)
-    assert set(creature_power_table.c.keys()) == set(
-        ['id', 'creature', 'name', 'description', 'hitpower'])
+    assert set(creature_power_table.c.keys()) == {
+        'id', 'creature', 'name', 'description', 'hitpower'}
     assert_col_type(creature_power_table.c.id, Integer)
     assert_col_type(creature_power_table.c.creature, Integer)
     assert creature_power_table.c.creature.nullable is False
@@ -760,8 +760,8 @@ def test_set1_to_set3():
     level_table = Table(
         'level', metadata,
         autoload=True, autoload_with=engine)
-    assert set(level_table.c.keys()) == set(
-        ['id', 'name', 'description'])
+    assert set(level_table.c.keys()) == {
+        'id', 'name', 'description'}
     assert_col_type(level_table.c.id, VARCHAR)
     assert level_table.c.id.primary_key is True
     assert_col_type(level_table.c.name, VARCHAR)
@@ -771,8 +771,8 @@ def test_set1_to_set3():
     level_exit_table = Table(
         'level_exit', metadata,
         autoload=True, autoload_with=engine)
-    assert set(level_exit_table.c.keys()) == set(
-        ['id', 'name', 'from_level', 'to_level'])
+    assert set(level_exit_table.c.keys()) == {
+        'id', 'name', 'from_level', 'to_level'}
     assert_col_type(level_exit_table.c.id, Integer)
     assert_col_type(level_exit_table.c.name, VARCHAR)
     assert_col_type(level_exit_table.c.from_level, VARCHAR)
@@ -788,40 +788,40 @@ def test_set1_to_set3():
 
     # Then make sure the models have been migrated correctly
     creature = session.query(Creature3).filter_by(
-        name=u'centipede').one()
+        name='centipede').one()
     assert creature.num_limbs == 100.0
     assert creature.magical_powers == []
 
     creature = session.query(Creature3).filter_by(
-        name=u'wolf').one()
+        name='wolf').one()
     assert creature.num_limbs == 4.0
     assert creature.magical_powers == []
 
     creature = session.query(Creature3).filter_by(
-        name=u'wizardsnake').one()
+        name='wizardsnake').one()
     assert creature.num_limbs == 0.0
     assert creature.magical_powers == []
 
     level = session.query(Level3).filter_by(
-        id=u'necroplex').one()
-    assert level.name == u'The Necroplex'
-    assert level.description == u'A complex full of pure deathzone.'
+        id='necroplex').one()
+    assert level.name == 'The Necroplex'
+    assert level.description == 'A complex full of pure deathzone.'
     level_exits = _get_level3_exits(session, level)
     assert level_exits == {
-        u'deathwell': u'evilstorm',
-        u'portal': u'central_park'}
+        'deathwell': 'evilstorm',
+        'portal': 'central_park'}
 
     level = session.query(Level3).filter_by(
-        id=u'evilstorm').one()
-    assert level.name == u'Evil Storm'
-    assert level.description == u'A storm full of pure evil.'
+        id='evilstorm').one()
+    assert level.name == 'Evil Storm'
+    assert level.description == 'A storm full of pure evil.'
     level_exits = _get_level3_exits(session, level)
     assert level_exits == {}  # You still can't escape the evilstorm!
 
     level = session.query(Level3).filter_by(
-        id=u'central_park').one()
-    assert level.name == u'Central Park, NY, NY'
-    assert level.description == u"New York's friendly Central Park."
+        id='central_park').one()
+    assert level.name == 'Central Park, NY, NY'
+    assert level.description == "New York's friendly Central Park."
     level_exits = _get_level3_exits(session, level)
     assert level_exits == {
         'portal': 'necroplex'}
diff --git a/mediagoblin/tests/test_storage.py b/mediagoblin/tests/test_storage.py
index a4c3e4eb..4591cbdf 100644
--- a/mediagoblin/tests/test_storage.py
+++ b/mediagoblin/tests/test_storage.py
@@ -31,15 +31,15 @@ from mediagoblin import storage
 ################
 
 def test_clean_listy_filepath():
-    expected = [u'dir1', u'dir2', u'linooks.jpg']
+    expected = ['dir1', 'dir2', 'linooks.jpg']
     assert storage.clean_listy_filepath(
         ['dir1', 'dir2', 'linooks.jpg']) == expected
 
-    expected = [u'dir1', u'foo_.._nasty', u'linooks.jpg']
+    expected = ['dir1', 'foo_.._nasty', 'linooks.jpg']
     assert storage.clean_listy_filepath(
         ['/dir1/', 'foo/../nasty', 'linooks.jpg']) == expected
 
-    expected = [u'etc', u'passwd']
+    expected = ['etc', 'passwd']
     assert storage.clean_listy_filepath(
         ['../../../etc/', 'passwd']) == expected
 
@@ -47,7 +47,7 @@ def test_clean_listy_filepath():
         storage.clean_listy_filepath(['../../', 'linooks.jpg'])
 
 
-class FakeStorageSystem(object):
+class FakeStorageSystem:
     def __init__(self, foobie, blech, **kwargs):
         self.foobie = foobie
         self.blech = blech
@@ -80,8 +80,8 @@ def test_storage_system_from_config():
              'mediagoblin.tests.test_storage:FakeStorageSystem'})
     assert this_storage.foobie == 'eiboof'
     assert this_storage.blech == 'hcelb'
-    assert six.text_type(this_storage.__class__) == \
-        u""
+    assert str(this_storage.__class__) == \
+        ""
 
 
 ##########################
@@ -152,7 +152,7 @@ def test_basic_storage_get_unique_filepath():
     # now we want something new, with the same name!
     new_filepath = this_storage.get_unique_filepath(
         ['dir1', 'dir2', 'filename.txt'])
-    assert new_filepath[:-1] == [u'dir1', u'dir2']
+    assert new_filepath[:-1] == ['dir1', 'dir2']
 
     new_filename = new_filepath[-1]
     assert new_filename.endswith('filename.txt')
@@ -174,7 +174,7 @@ def test_basic_storage_get_file():
     with this_storage.get_file(filepath, 'r') as our_file:
         assert our_file.read() == b'First file'
     assert os.path.exists(os.path.join(tmpdir, 'dir1/dir2/ourfile.txt'))
-    with open(os.path.join(tmpdir, 'dir1/dir2/ourfile.txt'), 'r') as our_file:
+    with open(os.path.join(tmpdir, 'dir1/dir2/ourfile.txt')) as our_file:
         assert our_file.read() == 'First file'
 
     # Write to the same path but try to get a unique file.
@@ -186,7 +186,7 @@ def test_basic_storage_get_file():
     with this_storage.get_file(new_filepath, 'r') as our_file:
         assert our_file.read() == b'Second file'
     assert os.path.exists(os.path.join(tmpdir, *new_filepath))
-    with open(os.path.join(tmpdir, *new_filepath), 'r') as our_file:
+    with open(os.path.join(tmpdir, *new_filepath)) as our_file:
         assert our_file.read() == 'Second file'
 
     # Read from an existing file
@@ -306,8 +306,7 @@ def _test_copy_local_to_storage_works(tmpdir, this_storage):
     os.remove(local_filename)
 
     assert open(
-        os.path.join(tmpdir, 'dir1/dir2/copiedto.txt'),
-        'r').read() == 'haha'
+        os.path.join(tmpdir, 'dir1/dir2/copiedto.txt')).read() == 'haha'
 
     this_storage.delete_file(['dir1', 'dir2', 'copiedto.txt'])
     cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2'])
diff --git a/mediagoblin/tests/test_submission.py b/mediagoblin/tests/test_submission.py
index a7661e85..72faf1b9 100644
--- a/mediagoblin/tests/test_submission.py
+++ b/mediagoblin/tests/test_submission.py
@@ -48,7 +48,7 @@ import pytest
 import webtest.forms
 import pkg_resources
 try:
-    import mock
+    from unittest import mock
 except ImportError:
     import unittest.mock as mock
 
@@ -72,8 +72,8 @@ from mediagoblin.submit.lib import new_upload_entry, run_process_media
 from .resources import GOOD_JPG, GOOD_PNG, EVIL_FILE, EVIL_JPG, EVIL_PNG, \
     BIG_BLUE, GOOD_PDF, GPS_JPG, MED_PNG, BIG_PNG
 
-GOOD_TAG_STRING = u'yin,yang'
-BAD_TAG_STRING = six.text_type('rage,' + 'f' * 26 + 'u' * 26)
+GOOD_TAG_STRING = 'yin,yang'
+BAD_TAG_STRING = str('rage,' + 'f' * 26 + 'u' * 26)
 
 FORM_CONTEXT = ['mediagoblin/submit/start.html', 'submit_form']
 REQUEST_CONTEXT = ['mediagoblin/user_pages/user.html', 'request']
@@ -115,7 +115,7 @@ def get_sample_entry(user, media_type):
     entry = new_upload_entry(user)
     entry.media_type = media_type
     entry.title = 'testentry'
-    entry.description = u""
+    entry.description = ""
     entry.license = None
     entry.media_metadata = {}
     entry.save()
@@ -129,7 +129,7 @@ class BaseTestSubmission:
 
         # TODO: Possibly abstract into a decorator like:
         # @as_authenticated_user('chris')
-        fixture_add_user(privileges=[u'active',u'uploader', u'commenter'])
+        fixture_add_user(privileges=['active','uploader', 'commenter'])
 
         self.login()
 
@@ -143,12 +143,12 @@ class BaseTestSubmission:
         ####   totally stupid.
         ####   Also if we found a way to make this run it should be a
         ####   property.
-        return LocalUser.query.filter(LocalUser.username==u'chris').first()
+        return LocalUser.query.filter(LocalUser.username=='chris').first()
 
     def login(self):
         self.test_app.post(
             '/auth/login/', {
-                'username': u'chris',
+                'username': 'chris',
                 'password': 'toast'})
 
     def logout(self):
@@ -185,10 +185,10 @@ class BaseTestSubmission:
     def check_normal_upload(self, title, filename):
         response, context = self.do_post({'title': title}, do_follow=True,
                                          **self.upload_data(filename))
-        self.check_url(response, '/u/{0}/'.format(self.our_user().username))
+        self.check_url(response, '/u/{}/'.format(self.our_user().username))
         assert 'mediagoblin/user_pages/user.html' in context
         # Make sure the media view is at least reachable, logged in...
-        url = '/u/{0}/m/{1}/'.format(self.our_user().username,
+        url = '/u/{}/m/{}/'.format(self.our_user().username,
                                      title.lower().replace(' ', '-'))
         self.test_app.get(url)
         # ... and logged out too.
@@ -212,38 +212,38 @@ class TestSubmissionBasics(BaseTestSubmission):
         # Test blank form
         # ---------------
         response, form = self.do_post({}, *FORM_CONTEXT)
-        assert form.file.errors == [u'You must provide a file.']
+        assert form.file.errors == ['You must provide a file.']
 
         # Test blank file
         # ---------------
-        response, form = self.do_post({'title': u'test title'}, *FORM_CONTEXT)
-        assert form.file.errors == [u'You must provide a file.']
+        response, form = self.do_post({'title': 'test title'}, *FORM_CONTEXT)
+        assert form.file.errors == ['You must provide a file.']
 
     def test_normal_jpg(self):
         # User uploaded should be 0
         assert self.our_user().uploaded == 0
 
-        self.check_normal_upload(u'Normal upload 1', GOOD_JPG)
+        self.check_normal_upload('Normal upload 1', GOOD_JPG)
 
         # User uploaded should be the same as GOOD_JPG size in Mb
         file_size = os.stat(GOOD_JPG).st_size / (1024.0 * 1024)
-        file_size = float('{0:.2f}'.format(file_size))
+        file_size = float('{:.2f}'.format(file_size))
 
         # Reload user
         assert self.our_user().uploaded == file_size
 
     def test_public_id_populated(self):
         # Upload the image first.
-        response, request = self.do_post({'title': u'Balanced Goblin'},
+        response, request = self.do_post({'title': 'Balanced Goblin'},
                                          *REQUEST_CONTEXT, do_follow=True,
                                          **self.upload_data(GOOD_JPG))
-        media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
+        media = self.check_media(request, {'title': 'Balanced Goblin'}, 1)
 
         # Now check that the public_id attribute is set.
         assert media.public_id != None
 
     def test_normal_png(self):
-        self.check_normal_upload(u'Normal upload 2', GOOD_PNG)
+        self.check_normal_upload('Normal upload 2', GOOD_PNG)
 
     def test_default_upload_limits(self):
         self.user_upload_limits(uploaded=500)
@@ -251,10 +251,10 @@ class TestSubmissionBasics(BaseTestSubmission):
         # User uploaded should be 500
         assert self.our_user().uploaded == 500
 
-        response, context = self.do_post({'title': u'Normal upload 4'},
+        response, context = self.do_post({'title': 'Normal upload 4'},
                                          do_follow=True,
                                          **self.upload_data(GOOD_JPG))
-        self.check_url(response, '/u/{0}/'.format(self.our_user().username))
+        self.check_url(response, '/u/{}/'.format(self.our_user().username))
         assert 'mediagoblin/user_pages/user.html' in context
 
         # Shouldn't have uploaded
@@ -266,10 +266,10 @@ class TestSubmissionBasics(BaseTestSubmission):
         # User uploaded should be 25
         assert self.our_user().uploaded == 25
 
-        response, context = self.do_post({'title': u'Normal upload 5'},
+        response, context = self.do_post({'title': 'Normal upload 5'},
                                          do_follow=True,
                                          **self.upload_data(GOOD_JPG))
-        self.check_url(response, '/u/{0}/'.format(self.our_user().username))
+        self.check_url(response, '/u/{}/'.format(self.our_user().username))
         assert 'mediagoblin/user_pages/user.html' in context
 
         # Shouldn't have uploaded
@@ -281,23 +281,23 @@ class TestSubmissionBasics(BaseTestSubmission):
         # User uploaded should be 499
         assert self.our_user().uploaded == 499
 
-        response, context = self.do_post({'title': u'Normal upload 6'},
+        response, context = self.do_post({'title': 'Normal upload 6'},
                                          do_follow=False,
                                          **self.upload_data(MED_PNG))
         form = context['mediagoblin/submit/start.html']['submit_form']
-        assert form.file.errors == [u'Sorry, uploading this file will put you'
+        assert form.file.errors == ['Sorry, uploading this file will put you'
                                     ' over your upload limit.']
 
         # Shouldn't have uploaded
         assert self.our_user().uploaded == 499
 
     def test_big_file(self):
-        response, context = self.do_post({'title': u'Normal upload 7'},
+        response, context = self.do_post({'title': 'Normal upload 7'},
                                          do_follow=False,
                                          **self.upload_data(BIG_PNG))
 
         form = context['mediagoblin/submit/start.html']['submit_form']
-        assert form.file.errors == [u'Sorry, the file size is too big.']
+        assert form.file.errors == ['Sorry, the file size is too big.']
 
     def check_media(self, request, find_data, count=None):
         media = MediaEntry.query.filter_by(**find_data)
@@ -310,34 +310,34 @@ class TestSubmissionBasics(BaseTestSubmission):
     def test_tags(self):
         # Good tag string
         # --------
-        response, request = self.do_post({'title': u'Balanced Goblin 2',
+        response, request = self.do_post({'title': 'Balanced Goblin 2',
                                           'tags': GOOD_TAG_STRING},
                                          *REQUEST_CONTEXT, do_follow=True,
                                          **self.upload_data(GOOD_JPG))
-        media = self.check_media(request, {'title': u'Balanced Goblin 2'}, 1)
-        assert media.tags[0]['name'] == u'yin'
-        assert media.tags[0]['slug'] == u'yin'
+        media = self.check_media(request, {'title': 'Balanced Goblin 2'}, 1)
+        assert media.tags[0]['name'] == 'yin'
+        assert media.tags[0]['slug'] == 'yin'
 
-        assert media.tags[1]['name'] == u'yang'
-        assert media.tags[1]['slug'] == u'yang'
+        assert media.tags[1]['name'] == 'yang'
+        assert media.tags[1]['slug'] == 'yang'
 
         # Test tags that are too long
         # ---------------
-        response, form = self.do_post({'title': u'Balanced Goblin 2',
+        response, form = self.do_post({'title': 'Balanced Goblin 2',
                                        'tags': BAD_TAG_STRING},
                                       *FORM_CONTEXT,
                                       **self.upload_data(GOOD_JPG))
         assert form.tags.errors == [
-                u'Tags must be shorter than 50 characters.  ' \
+                'Tags must be shorter than 50 characters.  ' \
                     'Tags that are too long: ' \
                     'ffffffffffffffffffffffffffuuuuuuuuuuuuuuuuuuuuuuuuuu']
 
     def test_delete(self):
         self.user_upload_limits(uploaded=50)
-        response, request = self.do_post({'title': u'Balanced Goblin'},
+        response, request = self.do_post({'title': 'Balanced Goblin'},
                                          *REQUEST_CONTEXT, do_follow=True,
                                          **self.upload_data(GOOD_JPG))
-        media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
+        media = self.check_media(request, {'title': 'Balanced Goblin'}, 1)
         media_id = media.id
 
         # render and post to the edit page.
@@ -346,11 +346,11 @@ class TestSubmissionBasics(BaseTestSubmission):
             user=self.our_user().username, media_id=media_id)
         self.test_app.get(edit_url)
         self.test_app.post(edit_url,
-            {'title': u'Balanced Goblin',
-             'slug': u"Balanced=Goblin",
-             'tags': u''})
-        media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
-        assert media.slug == u"balanced-goblin"
+            {'title': 'Balanced Goblin',
+             'slug': "Balanced=Goblin",
+             'tags': ''})
+        media = self.check_media(request, {'title': 'Balanced Goblin'}, 1)
+        assert media.slug == "balanced-goblin"
 
         # Add a comment, so we can test for its deletion later.
         self.check_comments(request, media_id, 0)
@@ -368,7 +368,7 @@ class TestSubmissionBasics(BaseTestSubmission):
             user=self.our_user().username, media_id=media_id)
         # Empty data means don't confirm
         response = self.do_post({}, do_follow=True, url=delete_url)[0]
-        media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
+        media = self.check_media(request, {'title': 'Balanced Goblin'}, 1)
         media_id = media.id
 
         # Confirm deletion
@@ -384,7 +384,7 @@ class TestSubmissionBasics(BaseTestSubmission):
     def test_evil_file(self):
         # Test non-suppoerted file with non-supported extension
         # -----------------------------------------------------
-        response, form = self.do_post({'title': u'Malicious Upload 1'},
+        response, form = self.do_post({'title': 'Malicious Upload 1'},
                                       *FORM_CONTEXT,
                                       **self.upload_data(EVIL_FILE))
         assert len(form.file.errors) == 1
@@ -395,12 +395,12 @@ class TestSubmissionBasics(BaseTestSubmission):
     def test_get_media_manager(self):
         """Test if the get_media_manger function returns sensible things
         """
-        response, request = self.do_post({'title': u'Balanced Goblin'},
+        response, request = self.do_post({'title': 'Balanced Goblin'},
                                          *REQUEST_CONTEXT, do_follow=True,
                                          **self.upload_data(GOOD_JPG))
-        media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
+        media = self.check_media(request, {'title': 'Balanced Goblin'}, 1)
 
-        assert media.media_type == u'mediagoblin.media_types.image'
+        assert media.media_type == 'mediagoblin.media_types.image'
         assert isinstance(media.media_manager, ImageMediaManager)
         assert media.media_manager.entry == media
 
@@ -412,7 +412,7 @@ class TestSubmissionBasics(BaseTestSubmission):
         template.clear_test_template_context()
         response = self.test_app.post(
             '/submit/', {
-                'title': u'UNIQUE_TITLE_PLS_DONT_CREATE_OTHER_MEDIA_WITH_THIS_TITLE'
+                'title': 'UNIQUE_TITLE_PLS_DONT_CREATE_OTHER_MEDIA_WITH_THIS_TITLE'
                 }, upload_files=[(
                     'file', GOOD_JPG)])
 
@@ -423,7 +423,7 @@ class TestSubmissionBasics(BaseTestSubmission):
         request = context['request']
 
         media = request.db.MediaEntry.query.filter_by(
-            title=u'UNIQUE_TITLE_PLS_DONT_CREATE_OTHER_MEDIA_WITH_THIS_TITLE').first()
+            title='UNIQUE_TITLE_PLS_DONT_CREATE_OTHER_MEDIA_WITH_THIS_TITLE').first()
 
         assert media.media_type == 'mediagoblin.media_types.image'
 
@@ -433,31 +433,31 @@ class TestSubmissionBasics(BaseTestSubmission):
         #   they'll be caught as failures during the processing step.
         response, context = self.do_post({'title': title}, do_follow=True,
                                          **self.upload_data(filename))
-        self.check_url(response, '/u/{0}/'.format(self.our_user().username))
+        self.check_url(response, '/u/{}/'.format(self.our_user().username))
         entry = mg_globals.database.MediaEntry.query.filter_by(title=title).first()
         assert entry.state == 'failed'
-        assert entry.fail_error == u'mediagoblin.processing:BadMediaFail'
+        assert entry.fail_error == 'mediagoblin.processing:BadMediaFail'
 
     def test_evil_jpg(self):
         # Test non-supported file with .jpg extension
         # -------------------------------------------
-        self.check_false_image(u'Malicious Upload 2', EVIL_JPG)
+        self.check_false_image('Malicious Upload 2', EVIL_JPG)
 
     def test_evil_png(self):
         # Test non-supported file with .png extension
         # -------------------------------------------
-        self.check_false_image(u'Malicious Upload 3', EVIL_PNG)
+        self.check_false_image('Malicious Upload 3', EVIL_PNG)
 
     def test_media_data(self):
-        self.check_normal_upload(u"With GPS data", GPS_JPG)
-        media = self.check_media(None, {"title": u"With GPS data"}, 1)
+        self.check_normal_upload("With GPS data", GPS_JPG)
+        media = self.check_media(None, {"title": "With GPS data"}, 1)
         assert media.get_location.position["latitude"] == 59.336666666666666
 
     def test_processing(self):
         public_store_dir = mg_globals.global_config[
             'storage:publicstore']['base_dir']
 
-        data = {'title': u'Big Blue'}
+        data = {'title': 'Big Blue'}
         response, request = self.do_post(data, *REQUEST_CONTEXT, do_follow=True,
                                          **self.upload_data(BIG_BLUE))
         media = self.check_media(request, data, 1)
@@ -497,8 +497,8 @@ class TestSubmissionBasics(BaseTestSubmission):
         # Collection option should be present if the user has collections. It
         # shouldn't allow other users' collections to be selected.
         col = fixture_add_collection(user=self.our_user())
-        user = fixture_add_user(username=u'different')
-        fixture_add_collection(user=user, name=u'different')
+        user = fixture_add_user(username='different')
+        fixture_add_collection(user=user, name='different')
         response = self.test_app.get('/submit/')
         form = response.form
         assert 'collection' in form.fields
@@ -522,7 +522,7 @@ class TestSubmissionBasics(BaseTestSubmission):
         # collection. That should be the last activity.
         assert Activity.query.order_by(
             Activity.id.desc()
-        ).first().content == '{0} added new picture to {1}'.format(
+        ).first().content == '{} added new picture to {}'.format(
             self.our_user().username, col.title)
 
         # Test upload succeeds if the user has collection and no collection is
@@ -547,7 +547,7 @@ class TestSubmissionVideo(BaseTestSubmission):
 
         # TODO: Possibly abstract into a decorator like:
         # @as_authenticated_user('chris')
-        fixture_add_user(privileges=[u'active',u'uploader', u'commenter'])
+        fixture_add_user(privileges=['active','uploader', 'commenter'])
 
         self.login()
 
@@ -558,7 +558,7 @@ class TestSubmissionVideo(BaseTestSubmission):
             self.check_normal_upload('Video', path)
 
         media = mg_globals.database.MediaEntry.query.filter_by(
-            title=u'Video').first()
+            title='Video').first()
 
         video_config = mg_globals.global_config['plugins'][self.media_type]
         for each_res in video_config['available_resolutions']:
@@ -573,7 +573,7 @@ class TestSubmissionVideo(BaseTestSubmission):
             self.check_normal_upload('testgetallmedia', path)
 
         media = mg_globals.database.MediaEntry.query.filter_by(
-            title=u'testgetallmedia').first()
+            title='testgetallmedia').first()
         result = media.get_all_media()
         video_config = mg_globals.global_config['plugins'][self.media_type]
 
@@ -595,7 +595,7 @@ class TestSubmissionVideo(BaseTestSubmission):
             assert len(result) == len(video_config['available_resolutions'])
             for i in range(len(video_config['available_resolutions'])):
                 media_file = MediaFile.query.filter_by(media_entry=media.id,
-                                                       name=('webm_{0}'.format(str(result[i][0])))).first()
+                                                       name=('webm_{}'.format(str(result[i][0])))).first()
                 # check media_file label
                 assert result[i][0] == video_config['available_resolutions'][i]
                 # check dimensions of media_file
@@ -645,7 +645,7 @@ class TestSubmissionVideo(BaseTestSubmission):
         mock_comp_task.assert_has_calls(calls)
         mock_cleanup.assert_called_once_with(args=(entry.id,), queue='default',
                                              immutable=True)
-        assert entry.state == u'processing'
+        assert entry.state == 'processing'
 
         # delete the entry
         entry.delete()
@@ -738,7 +738,7 @@ class TestSubmissionAudio(BaseTestSubmission):
 
         # TODO: Possibly abstract into a decorator like:
         # @as_authenticated_user('chris')
-        fixture_add_user(privileges=[u'active',u'uploader', u'commenter'])
+        fixture_add_user(privileges=['active','uploader', 'commenter'])
 
         self.login()
 
@@ -756,7 +756,7 @@ class TestSubmissionAudioVideo(BaseTestSubmission):
 
         # TODO: Possibly abstract into a decorator like:
         # @as_authenticated_user('chris')
-        fixture_add_user(privileges=[u'active',u'uploader', u'commenter'])
+        fixture_add_user(privileges=['active','uploader', 'commenter'])
 
         self.login()
 
@@ -774,15 +774,15 @@ class TestSubmissionPDF(BaseTestSubmission):
 
         # TODO: Possibly abstract into a decorator like:
         # @as_authenticated_user('chris')
-        fixture_add_user(privileges=[u'active',u'uploader', u'commenter'])
+        fixture_add_user(privileges=['active','uploader', 'commenter'])
 
         self.login()
 
     @pytest.mark.skipif("not os.path.exists(GOOD_PDF) or not pdf_check_prerequisites()")
     def test_normal_pdf(self):
-        response, context = self.do_post({'title': u'Normal upload 3 (pdf)'},
+        response, context = self.do_post({'title': 'Normal upload 3 (pdf)'},
                                          do_follow=True,
                                          **self.upload_data(GOOD_PDF))
-        self.check_url(response, '/u/{0}/'.format(self.our_user().username))
+        self.check_url(response, '/u/{}/'.format(self.our_user().username))
         assert 'mediagoblin/user_pages/user.html' in context
 
diff --git a/mediagoblin/tests/test_subtitles.py b/mediagoblin/tests/test_subtitles.py
index 4e884d07..87bf71df 100644
--- a/mediagoblin/tests/test_subtitles.py
+++ b/mediagoblin/tests/test_subtitles.py
@@ -25,12 +25,12 @@ from mediagoblin.plugins.subtitles.tools import open_subtitle, save_subtitle
 # Checking if the subtitle entry is working
 
 def test_add_subtitle_entry(test_app):
-    user_a = fixture_add_user(u"test_user")
+    user_a = fixture_add_user("test_user")
 
     media = fixture_media_entry(uploader=user_a.id, save=False, expunge=False)
     media.subtitle_files.append(dict(
-            name=u"some name",
-            filepath=[u"does", u"not", u"exist"],
+            name="some name",
+            filepath=["does", "not", "exist"],
             ))
     Session.add(media)
     Session.flush()
@@ -54,12 +54,12 @@ def test_read_write_file(test_app):
 # Checking the customize exceptions
 
 def test_customize_subtitle(test_app):
-    user_a = fixture_add_user(u"test_user")
+    user_a = fixture_add_user("test_user")
 
     media = fixture_media_entry(uploader=user_a.id, save=False, expunge=False)
     media.subtitle_files.append(dict(
-            name=u"some name",
-            filepath=[u"does", u"not", u"exist"],
+            name="some name",
+            filepath=["does", "not", "exist"],
             ))
     Session.add(media)
     Session.flush()
diff --git a/mediagoblin/tests/test_tags.py b/mediagoblin/tests/test_tags.py
index 8358b052..87edf196 100644
--- a/mediagoblin/tests/test_tags.py
+++ b/mediagoblin/tests/test_tags.py
@@ -25,19 +25,19 @@ def test_list_of_dicts_conversion(test_app):
     """
     # Leading, trailing, and internal whitespace should be removed and slugified
     assert text.convert_to_tag_list_of_dicts('sleep , 6    AM, chainsaw! ') == [
-                              {'name': u'sleep', 'slug': u'sleep'},
-                              {'name': u'6 AM', 'slug': u'6-am'},
-                              {'name': u'chainsaw!', 'slug': u'chainsaw'}]
+                              {'name': 'sleep', 'slug': 'sleep'},
+                              {'name': '6 AM', 'slug': '6-am'},
+                              {'name': 'chainsaw!', 'slug': 'chainsaw'}]
 
     # If the user enters two identical tags, record only one of them
-    assert text.convert_to_tag_list_of_dicts('echo,echo') == [{'name': u'echo',
-                                                               'slug': u'echo'}]
+    assert text.convert_to_tag_list_of_dicts('echo,echo') == [{'name': 'echo',
+                                                               'slug': 'echo'}]
 
     # When checking for duplicates, use the slug, not the tag
-    assert text.convert_to_tag_list_of_dicts('echo,#echo') == [{'name': u'#echo',
-                                                                'slug': u'echo'}]
+    assert text.convert_to_tag_list_of_dicts('echo,#echo') == [{'name': '#echo',
+                                                                'slug': 'echo'}]
 
     # Make sure converting the list of dicts to a string works
-    assert text.media_tags_as_string([{'name': u'yin', 'slug': u'yin'},
-                                      {'name': u'yang', 'slug': u'yang'}]) == \
-                                      u'yin, yang'
+    assert text.media_tags_as_string([{'name': 'yin', 'slug': 'yin'},
+                                      {'name': 'yang', 'slug': 'yang'}]) == \
+                                      'yin, yang'
diff --git a/mediagoblin/tests/test_tools.py b/mediagoblin/tests/test_tools.py
index 5f916400..ffbd184c 100644
--- a/mediagoblin/tests/test_tools.py
+++ b/mediagoblin/tests/test_tools.py
@@ -14,10 +14,9 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see .
 
-from __future__ import absolute_import, unicode_literals
 
 try:
-    import mock
+    from unittest import mock
 except ImportError:
     import unittest.mock as mock
 
@@ -27,7 +26,7 @@ from werkzeug.test import EnvironBuilder
 from mediagoblin.tools.request import decode_request
 from mediagoblin.tools.pagination import Pagination
 
-class TestDecodeRequest(object):
+class TestDecodeRequest:
     """Test the decode_request function."""
 
     def test_form_type(self):
@@ -67,7 +66,7 @@ class TestDecodeRequest(object):
         assert data['foo'] == 'bar'
 
 
-class TestPagination(object):
+class TestPagination:
     def _create_paginator(self, num_items, page, per_page):
         """Create a Paginator with a mock database cursor."""
         mock_cursor = mock.MagicMock()
diff --git a/mediagoblin/tests/test_util.py b/mediagoblin/tests/test_util.py
index 02976405..e1d2bdc1 100644
--- a/mediagoblin/tests/test_util.py
+++ b/mediagoblin/tests/test_util.py
@@ -15,7 +15,7 @@
 # along with this program.  If not, see .
 
 try:
-    import mock
+    from unittest import mock
 except ImportError:
     import unittest.mock as mock
 import email
@@ -35,14 +35,14 @@ testing._activate_testing()
 
 def _import_component_testing_method(silly_string):
     # Just for the sake of testing that our component importer works.
-    return u"'%s' is the silliest string I've ever seen" % silly_string
+    return "'%s' is the silliest string I've ever seen" % silly_string
 
 
 def test_import_component():
     imported_func = common.import_component(
         'mediagoblin.tests.test_util:_import_component_testing_method')
     result = imported_func('hooobaladoobala')
-    expected = u"'hooobaladoobala' is the silliest string I've ever seen"
+    expected = "'hooobaladoobala' is the silliest string I've ever seen"
     assert result == expected
 
 
@@ -104,19 +104,19 @@ def test_email_force_starttls(starttls_enabled_app):
             )
 
 def test_slugify():
-    assert url.slugify(u'a walk in the park') == u'a-walk-in-the-park'
-    assert url.slugify(u'A Walk in the Park') == u'a-walk-in-the-park'
-    assert url.slugify(u'a  walk in the park') == u'a-walk-in-the-park'
-    assert url.slugify(u'a walk in-the-park') == u'a-walk-in-the-park'
-    assert url.slugify(u'a w@lk in the park?') == u'a-w-lk-in-the-park'
-    assert url.slugify(u'a walk in the par\u0107') == u'a-walk-in-the-parc'
-    assert url.slugify(u'\u00E0\u0042\u00E7\u010F\u00EB\u0066') == u'abcdef'
+    assert url.slugify('a walk in the park') == 'a-walk-in-the-park'
+    assert url.slugify('A Walk in the Park') == 'a-walk-in-the-park'
+    assert url.slugify('a  walk in the park') == 'a-walk-in-the-park'
+    assert url.slugify('a walk in-the-park') == 'a-walk-in-the-park'
+    assert url.slugify('a w@lk in the park?') == 'a-w-lk-in-the-park'
+    assert url.slugify('a walk in the par\u0107') == 'a-walk-in-the-parc'
+    assert url.slugify('\u00E0\u0042\u00E7\u010F\u00EB\u0066') == 'abcdef'
     # Russian
-    assert url.slugify(u'\u043f\u0440\u043e\u0433\u0443\u043b\u043a\u0430 '
-            u'\u0432 \u043f\u0430\u0440\u043a\u0435') == u'progulka-v-parke'
+    assert url.slugify('\u043f\u0440\u043e\u0433\u0443\u043b\u043a\u0430 '
+            '\u0432 \u043f\u0430\u0440\u043a\u0435') == 'progulka-v-parke'
     # Korean
-    assert (url.slugify(u'\uacf5\uc6d0\uc5d0\uc11c \uc0b0\ucc45') ==
-            u'gongweoneseo-sancaeg')
+    assert (url.slugify('\uacf5\uc6d0\uc5d0\uc11c \uc0b0\ucc45') ==
+            'gongweoneseo-sancaeg')
 
 def test_locale_to_lower_upper():
     """
@@ -147,17 +147,17 @@ def test_locale_to_lower_lower():
 def test_gettext_lazy_proxy():
     from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
     from mediagoblin.tools.translate import pass_to_ugettext, set_thread_locale
-    proxy = _(u"Password")
-    orig = u"Password"
+    proxy = _("Password")
+    orig = "Password"
 
     set_thread_locale("es")
-    p1 = six.text_type(proxy)
+    p1 = str(proxy)
     p1_should = pass_to_ugettext(orig)
     assert p1_should != orig, "Test useless, string not translated"
     assert p1 == p1_should
 
     set_thread_locale("sv")
-    p2 = six.text_type(proxy)
+    p2 = str(proxy)
     p2_should = pass_to_ugettext(orig)
     assert p2_should != orig, "Test broken, string not translated"
     assert p2 == p2_should
@@ -185,7 +185,7 @@ def test_html_cleaner():
         '

innocent link!

') -class TestMail(object): +class TestMail: """ Test mediagoblin's mail tool """ def test_no_mail_server(self): """ Tests that no smtp server is available """ diff --git a/mediagoblin/tests/test_workbench.py b/mediagoblin/tests/test_workbench.py index 3202e698..f8df009e 100644 --- a/mediagoblin/tests/test_workbench.py +++ b/mediagoblin/tests/test_workbench.py @@ -24,7 +24,7 @@ from mediagoblin.decorators import get_workbench from mediagoblin.tests.test_storage import get_tmp_filestorage, cleanup_storage -class TestWorkbench(object): +class TestWorkbench: def setup(self): self.workbench_base = tempfile.mkdtemp(prefix='gmg_workbench_testing') self.workbench_manager = workbench.WorkbenchManager( diff --git a/mediagoblin/tests/tools.py b/mediagoblin/tests/tools.py index 82def02c..626ff323 100644 --- a/mediagoblin/tests/tools.py +++ b/mediagoblin/tests/tools.py @@ -39,7 +39,7 @@ from mediagoblin.tools.crypto import random_string from datetime import datetime -MEDIAGOBLIN_TEST_DB_NAME = u'__mediagoblin_tests__' +MEDIAGOBLIN_TEST_DB_NAME = '__mediagoblin_tests__' TEST_SERVER_CONFIG = pkg_resources.resource_filename( 'mediagoblin.tests', 'test_paste.ini') TEST_APP_CONFIG = pkg_resources.resource_filename( @@ -147,20 +147,20 @@ def install_fixtures_simple(db, fixtures): """ Very simply install fixtures in the database """ - for collection_name, collection_fixtures in six.iteritems(fixtures): + for collection_name, collection_fixtures in fixtures.items(): collection = db[collection_name] for fixture in collection_fixtures: collection.insert(fixture) -def fixture_add_user(username=u'chris', password=u'toast', +def fixture_add_user(username='chris', password='toast', privileges=[], wants_comment_notification=True): # Reuse existing user or create a new one test_user = LocalUser.query.filter(LocalUser.username==username).first() if test_user is None: test_user = LocalUser() test_user.username = username - test_user.email = username + u'@example.com' + test_user.email = username + '@example.com' if password is not None: test_user.pw_hash = gen_password_hash(password) test_user.wants_comment_notification = wants_comment_notification @@ -218,9 +218,9 @@ def fixture_add_comment_notification(entry, subject, user, return cn -def fixture_media_entry(title=u"Some title", slug=None, +def fixture_media_entry(title="Some title", slug=None, uploader=None, save=True, gen_slug=True, - state=u'unprocessed', fake_upload=True, + state='unprocessed', fake_upload=True, expunge=True): """ Add a media entry for testing purposes. @@ -237,14 +237,14 @@ def fixture_media_entry(title=u"Some title", slug=None, entry.title = title entry.slug = slug entry.actor = uploader - entry.media_type = u'image' + entry.media_type = 'image' entry.state = state if fake_upload: entry.media_files = {'thumb': ['a', 'b', 'c.jpg'], 'medium': ['d', 'e', 'f.png'], 'original': ['g', 'h', 'i.png']} - entry.media_type = u'mediagoblin.media_types.image' + entry.media_type = 'mediagoblin.media_types.image' if gen_slug: entry.generate_slug() @@ -260,7 +260,7 @@ def fixture_media_entry(title=u"Some title", slug=None, return entry -def fixture_add_collection(name=u"My first Collection", user=None, +def fixture_add_collection(name="My first Collection", user=None, collection_type=Collection.USER_DEFINED_TYPE): if user is None: user = fixture_add_user() diff --git a/mediagoblin/tools/common.py b/mediagoblin/tools/common.py index 34586611..f34149b7 100644 --- a/mediagoblin/tools/common.py +++ b/mediagoblin/tools/common.py @@ -47,7 +47,7 @@ def simple_printer(string): sys.stdout.flush() -class CollectingPrinter(object): +class CollectingPrinter: """ Another printer object, this one useful for capturing output for examination during testing or otherwise. @@ -68,6 +68,6 @@ class CollectingPrinter(object): @property def combined_string(self): - return u''.join(self.collection) + return ''.join(self.collection) diff --git a/mediagoblin/tools/crypto.py b/mediagoblin/tools/crypto.py index 1107e200..4bc541f8 100644 --- a/mediagoblin/tools/crypto.py +++ b/mediagoblin/tools/crypto.py @@ -79,7 +79,7 @@ def setup_crypto(app_config): key_filepath = os.path.join(key_dir, 'itsdangeroussecret.bin') try: load_key(key_filepath) - except IOError as error: + except OSError as error: if error.errno != errno.ENOENT: raise create_key(key_dir, key_filepath) diff --git a/mediagoblin/tools/exif.py b/mediagoblin/tools/exif.py index 2215fb0c..cf739b07 100644 --- a/mediagoblin/tools/exif.py +++ b/mediagoblin/tools/exif.py @@ -84,7 +84,7 @@ def extract_exif(filename): try: with open(filename, 'rb') as image: return process_file(image, details=False) - except IOError: + except OSError: raise BadMediaFail(_('Could not read the image file.')) @@ -100,8 +100,8 @@ def clean_exif(exif): 'JPEGThumbnail', 'Thumbnail JPEGInterchangeFormat'] - return dict((key, _ifd_tag_to_dict(value)) for (key, value) - in six.iteritems(exif) if key not in disabled_tags) + return {key: _ifd_tag_to_dict(value) for (key, value) + in exif.items() if key not in disabled_tags} def _ifd_tag_to_dict(tag): @@ -117,7 +117,7 @@ def _ifd_tag_to_dict(tag): 'field_length': tag.field_length, 'values': None} - if isinstance(tag.printable, six.binary_type): + if isinstance(tag.printable, bytes): # Force it to be decoded as UTF-8 so that it'll fit into the DB data['printable'] = tag.printable.decode('utf8', 'replace') @@ -125,7 +125,7 @@ def _ifd_tag_to_dict(tag): data['values'] = [_ratio_to_list(val) if isinstance(val, Ratio) else val for val in tag.values] else: - if isinstance(tag.values, six.binary_type): + if isinstance(tag.values, bytes): # Force UTF-8, so that it fits into the DB data['values'] = tag.values.decode('utf8', 'replace') else: @@ -140,7 +140,7 @@ def _ratio_to_list(ratio): def get_useful(tags): from collections import OrderedDict - return OrderedDict((key, tag) for (key, tag) in six.iteritems(tags)) + return OrderedDict((key, tag) for (key, tag) in tags.items()) def get_gps_data(tags): @@ -162,7 +162,7 @@ def get_gps_data(tags): 'latitude': tags['GPS GPSLatitude'], 'longitude': tags['GPS GPSLongitude']} - for key, dat in six.iteritems(dms_data): + for key, dat in dms_data.items(): gps_data[key] = ( lambda v: safe_gps_ratio_divide(v[0]) \ diff --git a/mediagoblin/tools/files.py b/mediagoblin/tools/files.py index 0509a387..e2e07733 100644 --- a/mediagoblin/tools/files.py +++ b/mediagoblin/tools/files.py @@ -27,7 +27,7 @@ def delete_media_files(media): - media: A MediaEntry document """ no_such_files = [] - for listpath in six.itervalues(media.media_files): + for listpath in media.media_files.values(): try: mg_globals.public_store.delete_file( listpath) diff --git a/mediagoblin/tools/licenses.py b/mediagoblin/tools/licenses.py index 9c13c683..0940ac56 100644 --- a/mediagoblin/tools/licenses.py +++ b/mediagoblin/tools/licenses.py @@ -64,7 +64,7 @@ SORTED_LICENSES = [ # dict {uri: License,...} to enable fast license lookup by uri. Ideally, # we'd want to use an OrderedDict (python 2.7+) here to avoid having the # same data in two structures -SUPPORTED_LICENSES = dict(((l.uri, l) for l in SORTED_LICENSES)) +SUPPORTED_LICENSES = {l.uri: l for l in SORTED_LICENSES} def get_license_by_url(url): diff --git a/mediagoblin/tools/mail.py b/mediagoblin/tools/mail.py index 3dc180d8..3e46fb36 100644 --- a/mediagoblin/tools/mail.py +++ b/mediagoblin/tools/mail.py @@ -14,7 +14,6 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from __future__ import print_function, unicode_literals import socket import logging @@ -64,7 +63,7 @@ class NoSMTPServerError(MailError): pass -class FakeMhost(object): +class FakeMhost: """ Just a fake mail host so we can capture and test messages from send_email @@ -115,7 +114,7 @@ def send_email(from_addr, to_addrs, subject, message_body): mhost = smtp_init( mg_globals.app_config['email_smtp_host'], mg_globals.app_config['email_smtp_port']) - except socket.error as original_error: + except OSError as original_error: error_message = "Couldn't contact mail server on <{}>:<{}>".format( mg_globals.app_config['email_smtp_host'], mg_globals.app_config['email_smtp_port']) @@ -126,7 +125,7 @@ def send_email(from_addr, to_addrs, subject, message_body): if not mg_globals.app_config['email_smtp_host']: # e.g. host = '' try: mhost.connect() # We SMTP.connect explicitly - except socket.error as original_error: + except OSError as original_error: error_message = "Couldn't contact mail server on <{}>:<{}>".format( mg_globals.app_config['email_smtp_host'], mg_globals.app_config['email_smtp_port']) @@ -138,7 +137,7 @@ def send_email(from_addr, to_addrs, subject, message_body): except smtplib.SMTPException: # Only raise an exception if we're forced to if mg_globals.app_config['email_smtp_force_starttls']: - six.reraise(*sys.exc_info()) + raise if ((not common.TESTS_ENABLED) and (mg_globals.app_config['email_smtp_user'] diff --git a/mediagoblin/tools/metadata.py b/mediagoblin/tools/metadata.py index aeb4f829..5e5ad9fd 100644 --- a/mediagoblin/tools/metadata.py +++ b/mediagoblin/tools/metadata.py @@ -15,7 +15,6 @@ # along with this program. If not, see . -from io import open import os import copy import json @@ -65,8 +64,8 @@ class DefaultChecker(FormatChecker): checkers = copy.deepcopy(draft4_format_checker.checkers) -DefaultChecker.checkers[u"uri"] = (is_uri, ()) -DefaultChecker.checkers[u"date-time"] = (is_datetime, (ValueError, TypeError)) +DefaultChecker.checkers["uri"] = (is_uri, ()) +DefaultChecker.checkers["date-time"] = (is_datetime, (ValueError, TypeError)) DEFAULT_CHECKER = DefaultChecker() # Crappy default schema, checks for things we deem important @@ -219,5 +218,5 @@ def expand_json(metadata, context=DEFAULT_CONTEXT): def rdfa_to_readable(rdfa_predicate): - readable = rdfa_predicate.split(u":")[1].capitalize() + readable = rdfa_predicate.split(":")[1].capitalize() return readable diff --git a/mediagoblin/tools/pagination.py b/mediagoblin/tools/pagination.py index db5f69fb..5e859a5f 100644 --- a/mediagoblin/tools/pagination.py +++ b/mediagoblin/tools/pagination.py @@ -19,12 +19,12 @@ from math import ceil, floor from itertools import count from werkzeug.datastructures import MultiDict -from six.moves import range, urllib, zip +from six.moves import urllib PAGINATION_DEFAULT_PER_PAGE = 30 -class Pagination(object): +class Pagination: """ Pagination class for database queries. @@ -105,7 +105,7 @@ class Pagination(object): new_get_params = dict(get_params) or {} new_get_params['page'] = page_no - return "%s?%s" % ( + return "{}?{}".format( base_url, urllib.parse.urlencode(new_get_params)) def get_page_url(self, request, page_no): diff --git a/mediagoblin/tools/pluginapi.py b/mediagoblin/tools/pluginapi.py index 1eabe9f1..be125a0e 100644 --- a/mediagoblin/tools/pluginapi.py +++ b/mediagoblin/tools/pluginapi.py @@ -66,7 +66,7 @@ from mediagoblin import mg_globals _log = logging.getLogger(__name__) -class PluginManager(object): +class PluginManager: """Manager for plugin things .. Note:: @@ -128,7 +128,7 @@ class PluginManager(object): def register_route(self, route): """Registers a single route""" - _log.debug('registering route: {0}'.format(route)) + _log.debug('registering route: {}'.format(route)) self.routes.append(route) def get_routes(self): diff --git a/mediagoblin/tools/processing.py b/mediagoblin/tools/processing.py index 26d7bb9b..5af3b5ad 100644 --- a/mediagoblin/tools/processing.py +++ b/mediagoblin/tools/processing.py @@ -47,12 +47,12 @@ def json_processing_callback(entry): Send an HTTP post to the registered callback url, if any. ''' if not entry.processing_metadata: - _log.debug('No processing callback URL for {0}'.format(entry)) + _log.debug('No processing callback URL for {}'.format(entry)) return url = entry.processing_metadata[0].callback_url - _log.debug('Sending processing callback for {0} to {1}'.format( + _log.debug('Sending processing callback for {} to {}'.format( entry, url)) @@ -76,11 +76,11 @@ def json_processing_callback(entry): try: request.urlopen(request) - _log.debug('Processing callback for {0} sent'.format(entry)) + _log.debug('Processing callback for {} sent'.format(entry)) return True except request.HTTPError: - _log.error('Failed to send callback: {0}'.format( + _log.error('Failed to send callback: {}'.format( traceback.format_exc())) return False diff --git a/mediagoblin/tools/request.py b/mediagoblin/tools/request.py index 7e1973d3..51403f05 100644 --- a/mediagoblin/tools/request.py +++ b/mediagoblin/tools/request.py @@ -39,9 +39,9 @@ def setup_user_in_request(request): # If API request the user will be associated with the access token authorization = decode_authorization_header(request.headers) - if authorization.get(u"access_token"): + if authorization.get("access_token"): # Check authorization header. - token = authorization[u"oauth_token"] + token = authorization["oauth_token"] token = AccessToken.query.filter_by(token=token).first() if token is not None: request.user = token.user @@ -66,7 +66,7 @@ def decode_request(request): content_type, _ = parse_options_header(request.content_type) if content_type == json_encoded: - data = json.loads(six.text_type(data, "utf-8")) + data = json.loads(str(data, "utf-8")) elif content_type == form_encoded or content_type == "": data = request.form else: diff --git a/mediagoblin/tools/response.py b/mediagoblin/tools/response.py index 889938a8..93b9c6e7 100644 --- a/mediagoblin/tools/response.py +++ b/mediagoblin/tools/response.py @@ -27,7 +27,7 @@ from datetime import date class Response(wz_Response): """Set default response mimetype to HTML, otherwise we get text/plain""" - default_mimetype = u'text/html' + default_mimetype = 'text/html' def render_to_response(request, template, context, status=200, mimetype=None): @@ -106,7 +106,7 @@ def render_http_exception(request, exc, description): elif stock_desc and exc.code == 404: return render_404(request) - return render_error(request, title='{0} {1}'.format(exc.code, exc.name), + return render_error(request, title='{} {}'.format(exc.code, exc.name), err_msg=description, status=exc.code) @@ -154,7 +154,7 @@ def json_response(serializable, _disable_cors=False, *args, **kw): 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, X-Requested-With'} - for key, value in six.iteritems(cors_headers): + for key, value in cors_headers.items(): response.headers.set(key, value) return response diff --git a/mediagoblin/tools/routing.py b/mediagoblin/tools/routing.py index a2c89f2a..1c360bff 100644 --- a/mediagoblin/tools/routing.py +++ b/mediagoblin/tools/routing.py @@ -44,17 +44,17 @@ class MGRoute(Rule): if not (self.match_slash or path.endswith("/")): path = path + "/" - return super(MGRoute, self).match(path, *args, **kwargs) + return super().match(path, *args, **kwargs) def endpoint_to_controller(rule): endpoint = rule.endpoint view_func = rule.gmg_controller - _log.debug('endpoint: {0} view_func: {1}'.format(endpoint, view_func)) + _log.debug('endpoint: {} view_func: {}'.format(endpoint, view_func)) # import the endpoint, or if it's already a callable, call that - if isinstance(view_func, six.string_types): + if isinstance(view_func, str): view_func = import_component(view_func) rule.gmg_controller = view_func @@ -73,7 +73,7 @@ def mount(mountpoint, routes): Mount a bunch of routes to this mountpoint """ for endpoint, url, controller in routes: - url = "%s/%s" % (mountpoint.rstrip('/'), url.lstrip('/')) + url = "{}/{}".format(mountpoint.rstrip('/'), url.lstrip('/')) add_route(endpoint, url, controller) def extract_url_arguments(url, urlmap): diff --git a/mediagoblin/tools/session.py b/mediagoblin/tools/session.py index a57f69cc..64d88fdc 100644 --- a/mediagoblin/tools/session.py +++ b/mediagoblin/tools/session.py @@ -39,7 +39,7 @@ class Session(dict): self.save() -class SessionManager(object): +class SessionManager: def __init__(self, cookie_name='MGSession', namespace=None): if namespace is None: namespace = cookie_name diff --git a/mediagoblin/tools/staticdirect.py b/mediagoblin/tools/staticdirect.py index 881dd20e..545500bc 100644 --- a/mediagoblin/tools/staticdirect.py +++ b/mediagoblin/tools/staticdirect.py @@ -29,7 +29,7 @@ import six _log = logging.getLogger(__name__) -class StaticDirect(object): +class StaticDirect: """ Direct to a static resource. @@ -48,9 +48,9 @@ class StaticDirect(object): "http://example.org/themestatic/images/lollerskate.png" """ def __init__(self, domains): - self.domains = dict( - [(key, value.rstrip('/')) - for key, value in six.iteritems(domains)]) + self.domains = { + key: value.rstrip('/') + for key, value in domains.items()} self.cache = {} def __call__(self, filepath, domain=None): @@ -62,11 +62,11 @@ class StaticDirect(object): return static_direction def get(self, filepath, domain=None): - return '%s/%s' % ( + return '{}/{}'.format( self.domains[domain], filepath.lstrip('/')) -class PluginStatic(object): +class PluginStatic: """Pass this into the ``'static_setup'`` hook to register your plugin's static directory. diff --git a/mediagoblin/tools/subtitles.py b/mediagoblin/tools/subtitles.py index efafbeec..c6e420a5 100644 --- a/mediagoblin/tools/subtitles.py +++ b/mediagoblin/tools/subtitles.py @@ -10,7 +10,7 @@ def get_path(path): def open_subtitle(path): subtitle_path = get_path(path) - subtitle = open(subtitle_path,"r") # Opening the file using the absolute path + subtitle = open(subtitle_path) # Opening the file using the absolute path text = subtitle.read() return text diff --git a/mediagoblin/tools/template.py b/mediagoblin/tools/template.py index f2619808..1a335fe4 100644 --- a/mediagoblin/tools/template.py +++ b/mediagoblin/tools/template.py @@ -65,12 +65,8 @@ def get_jinja_env(app, template_loader, locale): 'jinja2.ext.i18n', 'jinja2.ext.autoescape', TemplateHookExtension] + local_exts) - if six.PY2: - template_env.install_gettext_callables(mg_globals.thread_scope.translations.ugettext, - mg_globals.thread_scope.translations.ungettext) - else: - template_env.install_gettext_callables(mg_globals.thread_scope.translations.gettext, - mg_globals.thread_scope.translations.ngettext) + template_env.install_gettext_callables(mg_globals.thread_scope.translations.gettext, + mg_globals.thread_scope.translations.ngettext) # All templates will know how to ... # ... fetch all waiting messages and remove them from the queue @@ -164,7 +160,7 @@ class TemplateHookExtension(Extension): ... will include all templates hooked into the comment_extras section. """ - tags = set(["template_hook"]) + tags = {"template_hook"} def parse(self, parser): includes = [] diff --git a/mediagoblin/tools/text.py b/mediagoblin/tools/text.py index 48a53d23..2ef14eb9 100644 --- a/mediagoblin/tools/text.py +++ b/mediagoblin/tools/text.py @@ -43,13 +43,13 @@ HTML_CLEANER = Cleaner( safe_attrs_only=True, add_nofollow=True, # for now host_whitelist=(), - whitelist_tags=set([])) + whitelist_tags=set()) def clean_html(html): # clean_html barfs on an empty string if not html: - return u'' + return '' return HTML_CLEANER.clean_html(html) @@ -65,7 +65,7 @@ def convert_to_tag_list_of_dicts(tag_string): if tag_string: # Strip out internal, trailing, and leading whitespace - stripped_tag_string = u' '.join(tag_string.strip().split()) + stripped_tag_string = ' '.join(tag_string.strip().split()) # Split the tag string into a list of tags for tag in stripped_tag_string.split(','): @@ -84,12 +84,12 @@ def media_tags_as_string(media_entry_tags): """ tags_string = '' if media_entry_tags: - tags_string = u', '.join([tag['name'] for tag in media_entry_tags]) + tags_string = ', '.join([tag['name'] for tag in media_entry_tags]) return tags_string TOO_LONG_TAG_WARNING = \ - u'Tags must be shorter than %s characters. Tags that are too long: %s' + 'Tags must be shorter than %s characters. Tags that are too long: %s' def tag_length_validator(form, field): @@ -119,6 +119,6 @@ def cleaned_markdown_conversion(text): # Markdown will do nothing with and clean_html can do nothing with # an empty string :) if not text: - return u'' + return '' return clean_html(UNSAFE_MARKDOWN_INSTANCE.convert(text)) diff --git a/mediagoblin/tools/timesince.py b/mediagoblin/tools/timesince.py index 7a8b3ff0..d020a334 100644 --- a/mediagoblin/tools/timesince.py +++ b/mediagoblin/tools/timesince.py @@ -26,7 +26,6 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from __future__ import unicode_literals import datetime import pytz diff --git a/mediagoblin/tools/translate.py b/mediagoblin/tools/translate.py index a5e56cfe..c383fce0 100644 --- a/mediagoblin/tools/translate.py +++ b/mediagoblin/tools/translate.py @@ -33,7 +33,7 @@ TRANSLATIONS_PATH = pkg_resources.resource_filename( 'mediagoblin', 'i18n') # Known RTL languages -KNOWN_RTL = set(["ar", "fa", "he", "iw", "ur", "yi", "ji"]) +KNOWN_RTL = {"ar", "fa", "he", "iw", "ur", "yi", "ji"} def is_rtl(lang): """Returns true when the local language is right to left""" @@ -54,11 +54,11 @@ class ReallyLazyProxy(LazyProxy): Like LazyProxy, except that it doesn't cache the value ;) """ def __init__(self, func, *args, **kwargs): - super(ReallyLazyProxy, self).__init__(func, *args, **kwargs) + super().__init__(func, *args, **kwargs) object.__setattr__(self, '_is_cache_enabled', False) def __repr__(self): - return "<%s for %s(%r, %r)>" % ( + return "<{} for {}({!r}, {!r})>".format( self.__class__.__name__, self._func, self._args, @@ -71,10 +71,10 @@ def locale_to_lower_upper(locale): """ if '-' in locale: lang, country = locale.split('-', 1) - return '%s_%s' % (lang.lower(), country.upper()) + return '{}_{}'.format(lang.lower(), country.upper()) elif '_' in locale: lang, country = locale.split('_', 1) - return '%s_%s' % (lang.lower(), country.upper()) + return '{}_{}'.format(lang.lower(), country.upper()) else: return locale.lower() @@ -85,7 +85,7 @@ def locale_to_lower_lower(locale): """ if '_' in locale: lang, country = locale.split('_', 1) - return '%s-%s' % (lang.lower(), country.lower()) + return '{}-{}'.format(lang.lower(), country.lower()) else: return locale.lower() diff --git a/mediagoblin/tools/url.py b/mediagoblin/tools/url.py index 4d97247a..fbfeefae 100644 --- a/mediagoblin/tools/url.py +++ b/mediagoblin/tools/url.py @@ -22,11 +22,11 @@ import six _punct_re = re.compile(r'[\t !"#:$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+') -def slugify(text, delim=u'-'): +def slugify(text, delim='-'): """ Generates an ASCII-only slug. Taken from http://flask.pocoo.org/snippets/5/ """ result = [] for word in _punct_re.split(text.lower()): result.extend(unidecode(word).split()) - return six.text_type(delim.join(result)) + return str(delim.join(result)) diff --git a/mediagoblin/tools/workbench.py b/mediagoblin/tools/workbench.py index f1ad6414..bcc48640 100644 --- a/mediagoblin/tools/workbench.py +++ b/mediagoblin/tools/workbench.py @@ -27,7 +27,7 @@ from mediagoblin._compat import py2_unicode @py2_unicode -class Workbench(object): +class Workbench: """ Represent the directory for the workbench @@ -42,7 +42,7 @@ class Workbench(object): self.dir = dir def __str__(self): - return six.text_type(self.dir) + return str(self.dir) def __repr__(self): try: @@ -137,7 +137,7 @@ class Workbench(object): self.destroy() -class WorkbenchManager(object): +class WorkbenchManager: """ A system for generating and destroying workbenches. diff --git a/mediagoblin/user_pages/forms.py b/mediagoblin/user_pages/forms.py index 77e280e1..94ee8ba6 100644 --- a/mediagoblin/user_pages/forms.py +++ b/mediagoblin/user_pages/forms.py @@ -22,9 +22,9 @@ class MediaCommentForm(wtforms.Form): comment_content = wtforms.TextAreaField( _('Comment'), [wtforms.validators.InputRequired()], - description=_(u'You can use ' - u'' - u'Markdown for formatting.')) + description=_('You can use ' + '' + 'Markdown for formatting.')) class ConfirmDeleteForm(wtforms.Form): confirm = wtforms.BooleanField( diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py index 62a4f151..599fda53 100644 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@ -59,7 +59,7 @@ def user_home(request, page): user = LocalUser.query.filter_by(username=request.matchdict['user']).first() if not user: return render_404(request) - elif not user.has_privilege(u'active'): + elif not user.has_privilege('active'): return render_to_response( request, 'mediagoblin/user_pages/user_nonactive.html', @@ -95,7 +95,7 @@ def user_gallery(request, page, url_user=None): tag = request.matchdict.get('tag', None) cursor = MediaEntry.query.filter_by( actor=url_user.id, - state=u'processed').order_by(MediaEntry.created.desc()) + state='processed').order_by(MediaEntry.created.desc()) # Filter potentially by tag too: if tag: @@ -171,7 +171,7 @@ def media_home(request, media, page, **kwargs): @get_media_entry_by_id -@user_has_privilege(u'commenter') +@user_has_privilege('commenter') def media_post_comment(request, media): """ recieves POST from a MediaEntry() comment form, saves the comment. @@ -180,12 +180,12 @@ def media_post_comment(request, media): raise MethodNotAllowed() # If media is not processed, return NotFound. - if not media.state == u'processed': + if not media.state == 'processed': return render_404(request) comment = request.db.TextComment() comment.actor = request.user.id - comment.content = six.text_type(request.form['comment_content']) + comment.content = str(request.form['comment_content']) # Show error message if commenting is disabled. if not mg_globals.app_config['allow_comments']: @@ -224,7 +224,7 @@ def media_preview_comment(request): if not request.is_xhr: return render_404(request) - comment = six.text_type(request.form['comment_content']) + comment = str(request.form['comment_content']) cleancomment = { "content":cleaned_markdown_conversion(comment)} return Response(json.dumps(cleancomment)) @@ -236,7 +236,7 @@ def media_collect(request, media): """Add media to collection submission""" # If media is not processed, return NotFound. - if not media.state == u'processed': + if not media.state == 'processed': return render_404(request) form = user_forms.MediaCollectForm(request.form) @@ -374,8 +374,8 @@ def media_confirm_delete(request, media): "that you were sure.")) return redirect_obj(request, media) - if ((request.user.has_privilege(u'admin') and - request.user.id != media.actor)): + if (request.user.has_privilege('admin') and + request.user.id != media.actor): messages.add_message( request, messages.WARNING, @@ -464,8 +464,8 @@ def collection_item_confirm_remove(request, collection_item): return redirect_obj(request, collection) - if ((request.user.has_privilege(u'admin') and - request.user.id != collection_item.in_collection.actor)): + if (request.user.has_privilege('admin') and + request.user.id != collection_item.in_collection.actor): messages.add_message( request, messages.WARNING, @@ -521,8 +521,8 @@ def collection_confirm_delete(request, collection): return redirect_obj(request, collection) - if ((request.user.has_privilege(u'admin') and - request.user.id != collection.actor)): + if (request.user.has_privilege('admin') and + request.user.id != collection.actor): messages.add_message( request, messages.WARNING, _("You are about to delete another user's collection. " @@ -544,12 +544,12 @@ def atom_feed(request): """ user = LocalUser.query.filter_by( username = request.matchdict['user']).first() - if not user or not user.has_privilege(u'active'): + if not user or not user.has_privilege('active'): return render_404(request) feed_title = "MediaGoblin Feed for user '%s'" % request.matchdict['user'] link = request.urlgen('mediagoblin.user_pages.user_home', qualified=True, user=request.matchdict['user']) - cursor = MediaEntry.query.filter_by(actor=user.id, state=u'processed') + cursor = MediaEntry.query.filter_by(actor=user.id, state='processed') cursor = cursor.order_by(MediaEntry.created.desc()) cursor = cursor.limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS) @@ -581,7 +581,7 @@ def atom_feed(request): # Include a thumbnail image in content. file_urls = get_media_file_paths(entry.media_files, request.urlgen) if 'thumb' in file_urls: - content = u' {desc}'.format( + content = ' {desc}'.format( thumb=file_urls['thumb'], desc=entry.description_html) else: content = entry.description_html @@ -614,7 +614,7 @@ def collection_atom_feed(request): """ user = LocalUser.query.filter_by( username = request.matchdict['user']).first() - if not user or not user.has_privilege(u'active'): + if not user or not user.has_privilege('active'): return render_404(request) collection = Collection.query.filter_by( @@ -647,7 +647,7 @@ def collection_atom_feed(request): "MediaGoblin: Feed for %s's collection %s" % (request.matchdict['user'], collection.title), feed_url=request.url, - id=u'tag:{host},{year}:gnu-mediagoblin.{user}.collection.{slug}'\ + id='tag:{host},{year}:gnu-mediagoblin.{user}.collection.{slug}'\ .format( host=request.host, year=collection.created.strftime('%Y'), @@ -690,7 +690,7 @@ def processing_panel(request, page, url_user): # # Make sure we have permission to access this user's panel. Only # admins and this user herself should be able to do so. - if not (user.id == request.user.id or request.user.has_privilege(u'admin')): + if not (user.id == request.user.id or request.user.has_privilege('admin')): # No? Simply redirect to this user's homepage. return redirect( request, 'mediagoblin.user_pages.user_home', @@ -721,7 +721,7 @@ def processing_panel(request, page, url_user): @allow_reporting @get_user_media_entry -@user_has_privilege(u'reporter') +@user_has_privilege('reporter') @get_optional_media_comment_by_id def file_a_report(request, media, comment): """ diff --git a/mediagoblin/views.py b/mediagoblin/views.py index 9e893d56..f38d9278 100644 --- a/mediagoblin/views.py +++ b/mediagoblin/views.py @@ -25,7 +25,7 @@ from mediagoblin.decorators import uses_pagination, user_not_banned @user_not_banned @uses_pagination def default_root_view(request, page): - cursor = request.db.query(MediaEntry).filter_by(state=u'processed').\ + cursor = request.db.query(MediaEntry).filter_by(state='processed').\ order_by(MediaEntry.created.desc()) pagination = Pagination(page, cursor) diff --git a/setup.py b/setup.py index 4eb05c97..a7c9f2aa 100644 --- a/setup.py +++ b/setup.py @@ -14,10 +14,8 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from __future__ import print_function from setuptools import setup, find_packages -from io import open import os import re @@ -29,7 +27,7 @@ VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]" def get_version(): - with open(VERSIONFILE, "rt") as fobj: + with open(VERSIONFILE) as fobj: verstrline = fobj.read() mo = re.search(VSRE, verstrline, re.M) if mo: