[odrs-web] Blacken the codebase



commit d2d8a89638e16223cfed24f9c5215d611a217740
Author: Richard Hughes <richard hughsie com>
Date:   Wed Mar 16 20:19:46 2022 +0000

    Blacken the codebase

 app_data/Makefile                             |  18 +
 app_data/migrations/env.py                    |  39 +-
 app_data/migrations/versions/036f0cd034e5_.py |  10 +-
 app_data/migrations/versions/19526c284b29_.py |  47 +-
 app_data/migrations/versions/1b966aab67a1_.py |  21 +-
 app_data/migrations/versions/64751cf97429_.py |   6 +-
 app_data/migrations/versions/6f54fde07d02_.py |  26 +-
 app_data/migrations/versions/7c3432c40267_.py |  13 +-
 app_data/migrations/versions/84deb10331db_.py |  27 +-
 app_data/migrations/versions/a22c286d8094_.py |   9 +-
 app_data/migrations/versions/b2d75ba212ed_.py |   9 +-
 app_data/migrations/versions/b63a028c3346_.py |  22 +-
 app_data/migrations/versions/b8243269e9cf_.py |  31 +-
 app_data/migrations/versions/bbbcd54c69ac_.py | 109 ++--
 app_data/migrations/versions/e37c745e3097_.py |  29 +-
 app_data/migrations/versions/e6fa15874247_.py |  41 +-
 app_data/migrations/versions/ef03b3a98056_.py | 230 +++----
 app_data/migrations/versions/f32bd8265c3b_.py |  21 +-
 app_data/odrs/__init__.py                     |  38 +-
 app_data/odrs/dbutils.py                      |  21 +-
 app_data/odrs/models.py                       | 214 ++++---
 app_data/odrs/tests/odrs_test.py              | 670 +++++++++++---------
 app_data/odrs/tests/util_test.py              |  49 +-
 app_data/odrs/util.py                         | 140 +++--
 app_data/odrs/views.py                        | 112 ++--
 app_data/odrs/views_admin.py                  | 863 +++++++++++++++-----------
 app_data/odrs/views_api.py                    | 486 +++++++++------
 27 files changed, 1933 insertions(+), 1368 deletions(-)
---
diff --git a/app_data/Makefile b/app_data/Makefile
index 9526480..423355e 100644
--- a/app_data/Makefile
+++ b/app_data/Makefile
@@ -42,3 +42,21 @@ check: $(PYTEST)
                --cov=odrs \
                --cov-report=html
        $(PYTHON) ./pylint_test.py
+
+blacken:
+       find migrations odrs -name '*.py' -exec ./env/bin/black {} \;
+
+codespell: $(CODESPELL)
+       $(CODESPELL) --write-changes --builtin en-GB_to_en-US --skip \
+       .git,\
+       .mypy_cache,\
+       .coverage,\
+       *.pyc,\
+       *.png,\
+       *.jpg,\
+       *.js,\
+       *.doctree,\
+       *.pdf,\
+       *.gz,\
+       *.ico,\
+       env
diff --git a/app_data/migrations/env.py b/app_data/migrations/env.py
index 5eeddd3..148c7ff 100644
--- a/app_data/migrations/env.py
+++ b/app_data/migrations/env.py
@@ -11,16 +11,18 @@ config = context.config
 # Interpret the config file for Python logging.
 # This line sets up loggers basically.
 fileConfig(config.config_file_name)
-logger = logging.getLogger('alembic.env')
+logger = logging.getLogger("alembic.env")
 
 # add your model's MetaData object here
 # for 'autogenerate' support
 # from myapp import mymodel
 # target_metadata = mymodel.Base.metadata
 from flask import current_app
-config.set_main_option('sqlalchemy.url',
-                       current_app.config.get('SQLALCHEMY_DATABASE_URI'))
-target_metadata = current_app.extensions['migrate'].db.metadata
+
+config.set_main_option(
+    "sqlalchemy.url", current_app.config.get("SQLALCHEMY_DATABASE_URI")
+)
+target_metadata = current_app.extensions["migrate"].db.metadata
 
 # other values from the config, defined by the needs of env.py,
 # can be acquired:
@@ -30,6 +32,7 @@ target_metadata = current_app.extensions['migrate'].db.metadata
 # set to True to detect type changes in a better way (with false positives)
 COMPARE_TYPES = False
 
+
 def run_migrations_offline():
     """Run migrations in 'offline' mode.
 
@@ -43,8 +46,7 @@ def run_migrations_offline():
 
     """
     url = config.get_main_option("sqlalchemy.url")
-    context.configure(compare_type=COMPARE_TYPES,
-                      url=url)
+    context.configure(compare_type=COMPARE_TYPES, url=url)
 
     with context.begin_transaction():
         context.run_migrations()
@@ -62,22 +64,26 @@ def run_migrations_online():
     # when there are no changes to the schema
     # reference: http://alembic.readthedocs.org/en/latest/cookbook.html
     def process_revision_directives(context, revision, directives):
-        if getattr(config.cmd_opts, 'autogenerate', False):
+        if getattr(config.cmd_opts, "autogenerate", False):
             script = directives[0]
             if script.upgrade_ops.is_empty():
                 directives[:] = []
-                logger.info('No changes in schema detected.')
+                logger.info("No changes in schema detected.")
 
-    engine = engine_from_config(config.get_section(config.config_ini_section),
-                                prefix='sqlalchemy.',
-                                poolclass=pool.NullPool)
+    engine = engine_from_config(
+        config.get_section(config.config_ini_section),
+        prefix="sqlalchemy.",
+        poolclass=pool.NullPool,
+    )
 
     connection = engine.connect()
-    context.configure(connection=connection,
-                      compare_type=COMPARE_TYPES,
-                      target_metadata=target_metadata,
-                      process_revision_directives=process_revision_directives,
-                      **current_app.extensions['migrate'].configure_args)
+    context.configure(
+        connection=connection,
+        compare_type=COMPARE_TYPES,
+        target_metadata=target_metadata,
+        process_revision_directives=process_revision_directives,
+        **current_app.extensions["migrate"].configure_args
+    )
 
     try:
         with context.begin_transaction():
@@ -85,6 +91,7 @@ def run_migrations_online():
     finally:
         connection.close()
 
+
 if context.is_offline_mode():
     run_migrations_offline()
 else:
diff --git a/app_data/migrations/versions/036f0cd034e5_.py b/app_data/migrations/versions/036f0cd034e5_.py
index 020e9f3..e775d87 100644
--- a/app_data/migrations/versions/036f0cd034e5_.py
+++ b/app_data/migrations/versions/036f0cd034e5_.py
@@ -7,15 +7,17 @@ Create Date: 2019-07-03 11:39:22.323579
 """
 
 # revision identifiers, used by Alembic.
-revision = '036f0cd034e5'
-down_revision = 'b63a028c3346'
+revision = "036f0cd034e5"
+down_revision = "b63a028c3346"
 
 from alembic import op
 import sqlalchemy as sa
 from sqlalchemy.dialects import mysql
 
+
 def upgrade():
-    op.create_foreign_key(None, 'votes', 'reviews', ['review_id'], ['review_id'])
+    op.create_foreign_key(None, "votes", "reviews", ["review_id"], ["review_id"])
+
 
 def downgrade():
-    op.drop_constraint(None, 'votes', type_='foreignkey')
+    op.drop_constraint(None, "votes", type_="foreignkey")
diff --git a/app_data/migrations/versions/19526c284b29_.py b/app_data/migrations/versions/19526c284b29_.py
index 55cdc8a..8df85cf 100644
--- a/app_data/migrations/versions/19526c284b29_.py
+++ b/app_data/migrations/versions/19526c284b29_.py
@@ -7,8 +7,8 @@ Create Date: 2019-07-02 10:51:23.952062
 """
 
 # revision identifiers, used by Alembic.
-revision = '19526c284b29'
-down_revision = '84deb10331db'
+revision = "19526c284b29"
+down_revision = "84deb10331db"
 
 from alembic import op
 import sqlalchemy as sa
@@ -18,60 +18,63 @@ from sqlalchemy.exc import InternalError
 from odrs import db
 from odrs.models import User, Moderator, Event, Review, Vote
 
+
 def _hash_to_id(user_hash):
     user = db.session.query(User).filter(User.user_hash == user_hash).first()
     if not user:
         return None
     return user.user_id
 
+
 def upgrade():
     try:
-        op.add_column('eventlog', sa.Column('user_id', sa.Integer(), nullable=True))
-        op.create_foreign_key(None, 'eventlog', 'users', ['user_id'], ['user_id'])
+        op.add_column("eventlog", sa.Column("user_id", sa.Integer(), nullable=True))
+        op.create_foreign_key(None, "eventlog", "users", ["user_id"], ["user_id"])
     except InternalError as _:
         pass
     try:
-        op.add_column('moderators', sa.Column('user_id', sa.Integer(), nullable=True))
-        op.create_foreign_key(None, 'moderators', 'users', ['user_id'], ['user_id'])
+        op.add_column("moderators", sa.Column("user_id", sa.Integer(), nullable=True))
+        op.create_foreign_key(None, "moderators", "users", ["user_id"], ["user_id"])
     except InternalError as _:
         pass
     try:
-        op.add_column('reviews', sa.Column('user_id', sa.Integer(), nullable=True))
-        op.create_foreign_key(None, 'reviews', 'users', ['user_id'], ['user_id'])
+        op.add_column("reviews", sa.Column("user_id", sa.Integer(), nullable=True))
+        op.create_foreign_key(None, "reviews", "users", ["user_id"], ["user_id"])
     except InternalError as _:
         pass
     try:
-        op.add_column('votes', sa.Column('user_id', sa.Integer(), nullable=True))
-        op.create_foreign_key(None, 'votes', 'users', ['user_id'], ['user_id'])
+        op.add_column("votes", sa.Column("user_id", sa.Integer(), nullable=True))
+        op.create_foreign_key(None, "votes", "users", ["user_id"], ["user_id"])
     except InternalError as _:
         pass
 
-    print('CONVERTING Event')
+    print("CONVERTING Event")
     for val in db.session.query(Event).all():
         val.user_id = _hash_to_id(val.user_hash)
     db.session.commit()
 
-    print('CONVERTING Moderator')
+    print("CONVERTING Moderator")
     for val in db.session.query(Moderator).all():
         val.user_id = _hash_to_id(val.user_hash)
     db.session.commit()
 
-    print('CONVERTING Review')
+    print("CONVERTING Review")
     for val in db.session.query(Review).all():
         val.user_id = _hash_to_id(val.user_hash)
     db.session.commit()
 
-    print('CONVERTING Vote')
+    print("CONVERTING Vote")
     for val in db.session.query(Vote).all():
         val.user_id = _hash_to_id(val.user_hash)
     db.session.commit()
 
+
 def downgrade():
-    op.drop_constraint(None, 'votes', type_='foreignkey')
-    op.drop_constraint(None, 'reviews', type_='foreignkey')
-    op.drop_constraint(None, 'moderators', type_='foreignkey')
-    op.drop_constraint(None, 'eventlog', type_='foreignkey')
-    op.drop_column('votes', 'user_id')
-    op.drop_column('reviews', 'user_id')
-    op.drop_column('moderators', 'user_id')
-    op.drop_column('eventlog', 'user_id')
+    op.drop_constraint(None, "votes", type_="foreignkey")
+    op.drop_constraint(None, "reviews", type_="foreignkey")
+    op.drop_constraint(None, "moderators", type_="foreignkey")
+    op.drop_constraint(None, "eventlog", type_="foreignkey")
+    op.drop_column("votes", "user_id")
+    op.drop_column("reviews", "user_id")
+    op.drop_column("moderators", "user_id")
+    op.drop_column("eventlog", "user_id")
diff --git a/app_data/migrations/versions/1b966aab67a1_.py b/app_data/migrations/versions/1b966aab67a1_.py
index 0e70b76..a03654b 100644
--- a/app_data/migrations/versions/1b966aab67a1_.py
+++ b/app_data/migrations/versions/1b966aab67a1_.py
@@ -7,8 +7,8 @@ Create Date: 2019-07-01 19:44:32.916028
 """
 
 # revision identifiers, used by Alembic.
-revision = '1b966aab67a1'
-down_revision = 'b8243269e9cf'
+revision = "1b966aab67a1"
+down_revision = "b8243269e9cf"
 
 from alembic import op
 import sqlalchemy as sa
@@ -16,17 +16,24 @@ from sqlalchemy.dialects import mysql
 
 from odrs import db
 
+
 class OldModerator(db.Model):
-    __tablename__ = 'moderators'
-    __table_args__ = {'mysql_character_set': 'utf8mb4',
-                      'extend_existing': True}
+    __tablename__ = "moderators"
+    __table_args__ = {"mysql_character_set": "utf8mb4", "extend_existing": True}
     email = db.Column(db.Text)
 
+
 def upgrade():
     for mod in db.session.query(OldModerator).all():
         mod.username = mod.email
     db.session.commit()
-    op.drop_column('moderators', 'email')
+    op.drop_column("moderators", "email")
+
 
 def downgrade():
-    op.add_column('moderators', sa.Column('email', mysql.MEDIUMTEXT(collation='utf8mb4_unicode_ci'), 
nullable=True))
+    op.add_column(
+        "moderators",
+        sa.Column(
+            "email", mysql.MEDIUMTEXT(collation="utf8mb4_unicode_ci"), nullable=True
+        ),
+    )
diff --git a/app_data/migrations/versions/64751cf97429_.py b/app_data/migrations/versions/64751cf97429_.py
index a67357e..22fad17 100644
--- a/app_data/migrations/versions/64751cf97429_.py
+++ b/app_data/migrations/versions/64751cf97429_.py
@@ -7,17 +7,19 @@ Create Date: 2019-07-03 14:24:53.549481
 """
 
 # revision identifiers, used by Alembic.
-revision = '64751cf97429'
-down_revision = '036f0cd034e5'
+revision = "64751cf97429"
+down_revision = "036f0cd034e5"
 
 from odrs import db
 from odrs.models import Review
 from odrs.util import _addr_hash
 
+
 def upgrade():
     for review in db.session.query(Review).all():
         review.user_addr = _addr_hash(review.user_addr_hash)
     db.session.commit()
 
+
 def downgrade():
     pass
diff --git a/app_data/migrations/versions/6f54fde07d02_.py b/app_data/migrations/versions/6f54fde07d02_.py
index 3a55ae1..ed0c489 100644
--- a/app_data/migrations/versions/6f54fde07d02_.py
+++ b/app_data/migrations/versions/6f54fde07d02_.py
@@ -7,8 +7,8 @@ Create Date: 2019-07-04 11:58:24.685366
 """
 
 # revision identifiers, used by Alembic.
-revision = '6f54fde07d02'
-down_revision = 'e6fa15874247'
+revision = "6f54fde07d02"
+down_revision = "e6fa15874247"
 
 from alembic import op
 import sqlalchemy as sa
@@ -18,21 +18,29 @@ from sqlalchemy.exc import InternalError
 from odrs import db
 from odrs.models import Analytic, Component
 
+
 def upgrade():
     try:
-        op.add_column('components', sa.Column('fetch_cnt', sa.Integer(), nullable=True))
+        op.add_column("components", sa.Column("fetch_cnt", sa.Integer(), nullable=True))
     except InternalError as e:
         print(str(e))
-    for component in db.session.query(Component).\
-                        filter(Component.app_id != '').\
-                        order_by(Component.app_id.asc()).all():
+    for component in (
+        db.session.query(Component)
+        .filter(Component.app_id != "")
+        .order_by(Component.app_id.asc())
+        .all()
+    ):
         fetch_cnt = 0
-        for val in db.session.query(Analytic.fetch_cnt).\
-                        filter(Analytic.app_id == component.app_id).all():
+        for val in (
+            db.session.query(Analytic.fetch_cnt)
+            .filter(Analytic.app_id == component.app_id)
+            .all()
+        ):
             fetch_cnt += val[0]
         component.fetch_cnt = fetch_cnt
         print(component.app_id, fetch_cnt)
     db.session.commit()
 
+
 def downgrade():
-    op.drop_column('components', 'fetch_cnt')
+    op.drop_column("components", "fetch_cnt")
diff --git a/app_data/migrations/versions/7c3432c40267_.py b/app_data/migrations/versions/7c3432c40267_.py
index e0d465d..4a89dc5 100644
--- a/app_data/migrations/versions/7c3432c40267_.py
+++ b/app_data/migrations/versions/7c3432c40267_.py
@@ -7,22 +7,24 @@ Create Date: 2019-07-05 14:29:46.410656
 """
 
 # revision identifiers, used by Alembic.
-revision = '7c3432c40267'
-down_revision = 'ef03b3a98056'
+revision = "7c3432c40267"
+down_revision = "ef03b3a98056"
 
 from odrs import db
 from odrs.models import Component
 
+
 def upgrade():
 
     seen = {}
-    for component in db.session.query(Component).\
-                        order_by(Component.review_cnt.asc()).all():
+    for component in (
+        db.session.query(Component).order_by(Component.review_cnt.asc()).all()
+    ):
         if component.app_id not in seen:
             seen[component.app_id] = component
             continue
         component_old = seen[component.app_id]
-        print('duplicate', component.app_id, component.review_cnt)
+        print("duplicate", component.app_id, component.review_cnt)
         if component.review_cnt and component_old.review_cnt:
             component_old.review_cnt += component.review_cnt
         elif component.review_cnt:
@@ -38,5 +40,6 @@ def upgrade():
         db.session.delete(component)
     db.session.commit()
 
+
 def downgrade():
     pass
diff --git a/app_data/migrations/versions/84deb10331db_.py b/app_data/migrations/versions/84deb10331db_.py
index 3d90ebe..3257fc8 100644
--- a/app_data/migrations/versions/84deb10331db_.py
+++ b/app_data/migrations/versions/84deb10331db_.py
@@ -7,22 +7,29 @@ Create Date: 2019-07-02 10:26:30.036790
 """
 
 # revision identifiers, used by Alembic.
-revision = '84deb10331db'
-down_revision = 'bbbcd54c69ac'
+revision = "84deb10331db"
+down_revision = "bbbcd54c69ac"
 
 from alembic import op
 import sqlalchemy as sa
 from sqlalchemy.dialects import mysql
 
+
 def upgrade():
-    op.alter_column('users', 'user_hash',
-               existing_type=mysql.TEXT(),
-               type_=sa.String(length=40),
-               existing_nullable=True)
+    op.alter_column(
+        "users",
+        "user_hash",
+        existing_type=mysql.TEXT(),
+        type_=sa.String(length=40),
+        existing_nullable=True,
+    )
 
 
 def downgrade():
-    op.alter_column('users', 'user_hash',
-               existing_type=sa.String(length=40),
-               type_=mysql.TEXT(),
-               existing_nullable=True)
+    op.alter_column(
+        "users",
+        "user_hash",
+        existing_type=sa.String(length=40),
+        type_=mysql.TEXT(),
+        existing_nullable=True,
+    )
diff --git a/app_data/migrations/versions/a22c286d8094_.py b/app_data/migrations/versions/a22c286d8094_.py
index e65e9ba..aa53cbc 100644
--- a/app_data/migrations/versions/a22c286d8094_.py
+++ b/app_data/migrations/versions/a22c286d8094_.py
@@ -7,16 +7,17 @@ Create Date: 2019-07-04 13:50:13.788206
 """
 
 # revision identifiers, used by Alembic.
-revision = 'a22c286d8094'
-down_revision = '6f54fde07d02'
+revision = "a22c286d8094"
+down_revision = "6f54fde07d02"
 
 from alembic import op
 import sqlalchemy as sa
 from sqlalchemy.dialects import mysql
 
+
 def upgrade():
-    op.drop_column('reviews', 'app_id')
+    op.drop_column("reviews", "app_id")
 
 
 def downgrade():
-    op.add_column('reviews', sa.Column('app_id', mysql.TEXT(), nullable=True))
+    op.add_column("reviews", sa.Column("app_id", mysql.TEXT(), nullable=True))
diff --git a/app_data/migrations/versions/b2d75ba212ed_.py b/app_data/migrations/versions/b2d75ba212ed_.py
index 265cd89..1b79e12 100644
--- a/app_data/migrations/versions/b2d75ba212ed_.py
+++ b/app_data/migrations/versions/b2d75ba212ed_.py
@@ -7,16 +7,17 @@ Create Date: 2019-07-04 09:07:17.032627
 """
 
 # revision identifiers, used by Alembic.
-revision = 'b2d75ba212ed'
-down_revision = 'e37c745e3097'
+revision = "b2d75ba212ed"
+down_revision = "e37c745e3097"
 
 from alembic import op
 import sqlalchemy as sa
 from sqlalchemy.dialects import mysql
 
+
 def upgrade():
-    op.add_column('taboos', sa.Column('severity', sa.Integer(), nullable=True))
+    op.add_column("taboos", sa.Column("severity", sa.Integer(), nullable=True))
 
 
 def downgrade():
-    op.drop_column('taboos', 'severity')
+    op.drop_column("taboos", "severity")
diff --git a/app_data/migrations/versions/b63a028c3346_.py b/app_data/migrations/versions/b63a028c3346_.py
index 8ed375b..10344e2 100644
--- a/app_data/migrations/versions/b63a028c3346_.py
+++ b/app_data/migrations/versions/b63a028c3346_.py
@@ -7,21 +7,23 @@ Create Date: 2019-07-02 11:13:57.117376
 """
 
 # revision identifiers, used by Alembic.
-revision = 'b63a028c3346'
-down_revision = '19526c284b29'
+revision = "b63a028c3346"
+down_revision = "19526c284b29"
 
 from alembic import op
 import sqlalchemy as sa
 from sqlalchemy.dialects import mysql
 
+
 def upgrade():
-    op.drop_column('eventlog', 'user_hash')
-    op.drop_column('moderators', 'user_hash')
-    op.drop_column('reviews', 'user_hash')
-    op.drop_column('votes', 'user_hash')
+    op.drop_column("eventlog", "user_hash")
+    op.drop_column("moderators", "user_hash")
+    op.drop_column("reviews", "user_hash")
+    op.drop_column("votes", "user_hash")
+
 
 def downgrade():
-    op.add_column('votes', sa.Column('user_hash', mysql.TEXT(), nullable=True))
-    op.add_column('reviews', sa.Column('user_hash', mysql.TEXT(), nullable=True))
-    op.add_column('moderators', sa.Column('user_hash', mysql.TEXT(), nullable=True))
-    op.add_column('eventlog', sa.Column('user_hash', mysql.TEXT(), nullable=True))
+    op.add_column("votes", sa.Column("user_hash", mysql.TEXT(), nullable=True))
+    op.add_column("reviews", sa.Column("user_hash", mysql.TEXT(), nullable=True))
+    op.add_column("moderators", sa.Column("user_hash", mysql.TEXT(), nullable=True))
+    op.add_column("eventlog", sa.Column("user_hash", mysql.TEXT(), nullable=True))
diff --git a/app_data/migrations/versions/b8243269e9cf_.py b/app_data/migrations/versions/b8243269e9cf_.py
index 1d31760..1dc1255 100644
--- a/app_data/migrations/versions/b8243269e9cf_.py
+++ b/app_data/migrations/versions/b8243269e9cf_.py
@@ -7,7 +7,7 @@ Create Date: 2019-06-28 12:39:37.287224
 """
 
 # revision identifiers, used by Alembic.
-revision = 'b8243269e9cf'
+revision = "b8243269e9cf"
 down_revision = None
 
 from alembic import op
@@ -18,23 +18,30 @@ from sqlalchemy.dialects import mysql
 from odrs import db
 from odrs.models import Review
 
+
 def upgrade():
-    op.alter_column('reviews', 'date_deleted',
-               existing_type=mysql.TIMESTAMP(),
-               nullable=True,
-               existing_server_default=sa.text("'0000-00-00 00:00:00'"))
+    op.alter_column(
+        "reviews",
+        "date_deleted",
+        existing_type=mysql.TIMESTAMP(),
+        nullable=True,
+        existing_server_default=sa.text("'0000-00-00 00:00:00'"),
+    )
     since = datetime.datetime.utcnow() - datetime.timedelta(hours=3)
     for review in db.session.query(Review).all():
-        if review.date_deleted == '0000-00-00 00:00:00':
-             review.date_deleted = None
+        if review.date_deleted == "0000-00-00 00:00:00":
+            review.date_deleted = None
         if review.date_deleted and review.date_deleted > since:
-             review.date_deleted = None
+            review.date_deleted = None
 
     db.session.commit()
 
 
 def downgrade():
-    op.alter_column('reviews', 'date_deleted',
-               existing_type=mysql.TIMESTAMP(),
-               nullable=False,
-               existing_server_default=sa.text("'0000-00-00 00:00:00'"))
+    op.alter_column(
+        "reviews",
+        "date_deleted",
+        existing_type=mysql.TIMESTAMP(),
+        nullable=False,
+        existing_server_default=sa.text("'0000-00-00 00:00:00'"),
+    )
diff --git a/app_data/migrations/versions/bbbcd54c69ac_.py b/app_data/migrations/versions/bbbcd54c69ac_.py
index 7f4bfd6..6b7d72b 100644
--- a/app_data/migrations/versions/bbbcd54c69ac_.py
+++ b/app_data/migrations/versions/bbbcd54c69ac_.py
@@ -7,54 +7,79 @@ Create Date: 2019-07-02 10:06:20.015220
 """
 
 # revision identifiers, used by Alembic.
-revision = 'bbbcd54c69ac'
-down_revision = '1b966aab67a1'
+revision = "bbbcd54c69ac"
+down_revision = "1b966aab67a1"
 
 from alembic import op
 import sqlalchemy as sa
 from sqlalchemy.dialects import mysql
 
+
 def upgrade():
-    op.alter_column('eventlog', 'important',
-               existing_type=mysql.INTEGER(display_width=11),
-               type_=sa.Boolean(),
-               existing_nullable=True,
-               existing_server_default=sa.text('0'))
-    op.alter_column('moderators', 'is_admin',
-               existing_type=mysql.TINYINT(display_width=4),
-               type_=sa.Boolean(),
-               existing_nullable=True,
-               existing_server_default=sa.text('0'))
-    op.alter_column('moderators', 'is_enabled',
-               existing_type=mysql.TINYINT(display_width=4),
-               type_=sa.Boolean(),
-               existing_nullable=True,
-               existing_server_default=sa.text('0'))
-    op.alter_column('users', 'is_banned',
-               existing_type=mysql.INTEGER(display_width=11),
-               type_=sa.Boolean(),
-               existing_nullable=True,
-               existing_server_default=sa.text('0'))
+    op.alter_column(
+        "eventlog",
+        "important",
+        existing_type=mysql.INTEGER(display_width=11),
+        type_=sa.Boolean(),
+        existing_nullable=True,
+        existing_server_default=sa.text("0"),
+    )
+    op.alter_column(
+        "moderators",
+        "is_admin",
+        existing_type=mysql.TINYINT(display_width=4),
+        type_=sa.Boolean(),
+        existing_nullable=True,
+        existing_server_default=sa.text("0"),
+    )
+    op.alter_column(
+        "moderators",
+        "is_enabled",
+        existing_type=mysql.TINYINT(display_width=4),
+        type_=sa.Boolean(),
+        existing_nullable=True,
+        existing_server_default=sa.text("0"),
+    )
+    op.alter_column(
+        "users",
+        "is_banned",
+        existing_type=mysql.INTEGER(display_width=11),
+        type_=sa.Boolean(),
+        existing_nullable=True,
+        existing_server_default=sa.text("0"),
+    )
 
 
 def downgrade():
-    op.alter_column('users', 'is_banned',
-               existing_type=sa.Boolean(),
-               type_=mysql.INTEGER(display_width=11),
-               existing_nullable=True,
-               existing_server_default=sa.text('0'))
-    op.alter_column('moderators', 'is_enabled',
-               existing_type=sa.Boolean(),
-               type_=mysql.TINYINT(display_width=4),
-               existing_nullable=True,
-               existing_server_default=sa.text('0'))
-    op.alter_column('moderators', 'is_admin',
-               existing_type=sa.Boolean(),
-               type_=mysql.TINYINT(display_width=4),
-               existing_nullable=True,
-               existing_server_default=sa.text('0'))
-    op.alter_column('eventlog', 'important',
-               existing_type=sa.Boolean(),
-               type_=mysql.INTEGER(display_width=11),
-               existing_nullable=True,
-               existing_server_default=sa.text('0'))
+    op.alter_column(
+        "users",
+        "is_banned",
+        existing_type=sa.Boolean(),
+        type_=mysql.INTEGER(display_width=11),
+        existing_nullable=True,
+        existing_server_default=sa.text("0"),
+    )
+    op.alter_column(
+        "moderators",
+        "is_enabled",
+        existing_type=sa.Boolean(),
+        type_=mysql.TINYINT(display_width=4),
+        existing_nullable=True,
+        existing_server_default=sa.text("0"),
+    )
+    op.alter_column(
+        "moderators",
+        "is_admin",
+        existing_type=sa.Boolean(),
+        type_=mysql.TINYINT(display_width=4),
+        existing_nullable=True,
+        existing_server_default=sa.text("0"),
+    )
+    op.alter_column(
+        "eventlog",
+        "important",
+        existing_type=sa.Boolean(),
+        type_=mysql.INTEGER(display_width=11),
+        existing_nullable=True,
+        existing_server_default=sa.text("0"),
+    )
diff --git a/app_data/migrations/versions/e37c745e3097_.py b/app_data/migrations/versions/e37c745e3097_.py
index a8bce80..99a611b 100644
--- a/app_data/migrations/versions/e37c745e3097_.py
+++ b/app_data/migrations/versions/e37c745e3097_.py
@@ -7,25 +7,28 @@ Create Date: 2019-07-03 19:54:01.718718
 """
 
 # revision identifiers, used by Alembic.
-revision = 'e37c745e3097'
-down_revision = '64751cf97429'
+revision = "e37c745e3097"
+down_revision = "64751cf97429"
 
 from alembic import op
 import sqlalchemy as sa
 from sqlalchemy.dialects import mysql
 
+
 def upgrade():
-    op.create_table('taboos',
-    sa.Column('taboo_id', sa.Integer(), nullable=False),
-    sa.Column('locale', sa.String(length=8), nullable=False),
-    sa.Column('value', sa.Text(), nullable=False),
-    sa.Column('description', sa.Text(), nullable=True),
-    sa.PrimaryKeyConstraint('taboo_id'),
-    sa.UniqueConstraint('taboo_id'),
-    mysql_character_set='utf8mb4'
+    op.create_table(
+        "taboos",
+        sa.Column("taboo_id", sa.Integer(), nullable=False),
+        sa.Column("locale", sa.String(length=8), nullable=False),
+        sa.Column("value", sa.Text(), nullable=False),
+        sa.Column("description", sa.Text(), nullable=True),
+        sa.PrimaryKeyConstraint("taboo_id"),
+        sa.UniqueConstraint("taboo_id"),
+        mysql_character_set="utf8mb4",
     )
-    op.create_index(op.f('ix_taboos_locale'), 'taboos', ['locale'], unique=False)
+    op.create_index(op.f("ix_taboos_locale"), "taboos", ["locale"], unique=False)
+
 
 def downgrade():
-    op.drop_index(op.f('ix_taboos_locale'), table_name='taboos')
-    op.drop_table('taboos')
+    op.drop_index(op.f("ix_taboos_locale"), table_name="taboos")
+    op.drop_table("taboos")
diff --git a/app_data/migrations/versions/e6fa15874247_.py b/app_data/migrations/versions/e6fa15874247_.py
index 927fa91..dd2f317 100644
--- a/app_data/migrations/versions/e6fa15874247_.py
+++ b/app_data/migrations/versions/e6fa15874247_.py
@@ -7,8 +7,8 @@ Create Date: 2019-07-04 10:44:23.739416
 """
 
 # revision identifiers, used by Alembic.
-revision = 'e6fa15874247'
-down_revision = 'b2d75ba212ed'
+revision = "e6fa15874247"
+down_revision = "b2d75ba212ed"
 
 from alembic import op
 import sqlalchemy as sa
@@ -18,21 +18,25 @@ from sqlalchemy.exc import InternalError
 from odrs import db
 from odrs.models import Review, Component
 
+
 def upgrade():
 
     try:
-        op.create_table('components',
-        sa.Column('component_id', sa.Integer(), nullable=False),
-        sa.Column('app_id', sa.Text(), nullable=True),
-        sa.Column('review_cnt', sa.Integer(), nullable=True),
-        sa.PrimaryKeyConstraint('component_id'),
-        sa.UniqueConstraint('component_id'),
-        mysql_character_set='utf8mb4'
+        op.create_table(
+            "components",
+            sa.Column("component_id", sa.Integer(), nullable=False),
+            sa.Column("app_id", sa.Text(), nullable=True),
+            sa.Column("review_cnt", sa.Integer(), nullable=True),
+            sa.PrimaryKeyConstraint("component_id"),
+            sa.UniqueConstraint("component_id"),
+            mysql_character_set="utf8mb4",
         )
     except InternalError as e:
         print(str(e))
     try:
-        op.add_column('reviews', sa.Column('component_id', sa.Integer(), nullable=False))
+        op.add_column(
+            "reviews", sa.Column("component_id", sa.Integer(), nullable=False)
+        )
     except InternalError as e:
         print(str(e))
 
@@ -45,7 +49,7 @@ def upgrade():
     reviews = db.session.query(Review).all()
     for review in reviews:
         if review._app_id not in app_ids:
-            print('adding', review._app_id)
+            print("adding", review._app_id)
             component = Component(review._app_id)
             db.session.add(component)
             app_ids[component.app_id] = component
@@ -61,11 +65,18 @@ def upgrade():
 
     # should all be valid now
     try:
-        op.create_foreign_key('components_ibfk_3', 'reviews', 'components', ['component_id'], 
['component_id'])
+        op.create_foreign_key(
+            "components_ibfk_3",
+            "reviews",
+            "components",
+            ["component_id"],
+            ["component_id"],
+        )
     except InternalError as e:
         print(str(e))
 
+
 def downgrade():
-    op.drop_constraint('components_ibfk_3', 'reviews', type_='foreignkey')
-    op.drop_column('reviews', 'component_id')
-    op.drop_table('components')
+    op.drop_constraint("components_ibfk_3", "reviews", type_="foreignkey")
+    op.drop_column("reviews", "component_id")
+    op.drop_table("components")
diff --git a/app_data/migrations/versions/ef03b3a98056_.py b/app_data/migrations/versions/ef03b3a98056_.py
index a5df523..f8635ea 100644
--- a/app_data/migrations/versions/ef03b3a98056_.py
+++ b/app_data/migrations/versions/ef03b3a98056_.py
@@ -7,127 +7,131 @@ Create Date: 2019-07-05 12:53:17.236904
 """
 
 # revision identifiers, used by Alembic.
-revision = 'ef03b3a98056'
-down_revision = 'f32bd8265c3b'
+revision = "ef03b3a98056"
+down_revision = "f32bd8265c3b"
 
 from odrs import db
 from odrs.models import Component
 
+
 def upgrade():
 
     # get all existing components
     components = {}
-    for component in db.session.query(Component).\
-                        filter(Component.app_id != '').\
-                        order_by(Component.app_id.asc()).all():
+    for component in (
+        db.session.query(Component)
+        .filter(Component.app_id != "")
+        .order_by(Component.app_id.asc())
+        .all()
+    ):
         components[component.app_id] = component
 
     # from appstream-glib
     mapping = {
-        'baobab.desktop': 'org.gnome.baobab.desktop',
-        'bijiben.desktop': 'org.gnome.bijiben.desktop',
-        'cheese.desktop': 'org.gnome.Cheese.desktop',
-        'devhelp.desktop': 'org.gnome.Devhelp.desktop',
-        'epiphany.desktop': 'org.gnome.Epiphany.desktop',
-        'file-roller.desktop': 'org.gnome.FileRoller.desktop',
-        'font-manager.desktop': 'org.gnome.FontManager.desktop',
-        'gcalctool.desktop': 'gnome-calculator.desktop',
-        'gcm-viewer.desktop': 'org.gnome.ColorProfileViewer.desktop',
-        'geary.desktop': 'org.gnome.Geary.desktop',
-        'gedit.desktop': 'org.gnome.gedit.desktop',
-        'glchess.desktop': 'gnome-chess.desktop',
-        'glines.desktop': 'five-or-more.desktop',
-        'gnect.desktop': 'four-in-a-row.desktop',
-        'gnibbles.desktop': 'gnome-nibbles.desktop',
-        'gnobots2.desktop': 'gnome-robots.desktop',
-        'gnome-2048.desktop': 'org.gnome.gnome-2048.desktop',
-        'gnome-boxes.desktop': 'org.gnome.Boxes.desktop',
-        'gnome-calculator.desktop': 'org.gnome.Calculator.desktop',
-        'gnome-clocks.desktop': 'org.gnome.clocks.desktop',
-        'gnome-contacts.desktop': 'org.gnome.Contacts.desktop',
-        'gnome-dictionary.desktop': 'org.gnome.Dictionary.desktop',
-        'gnome-disks.desktop': 'org.gnome.DiskUtility.desktop',
-        'gnome-documents.desktop': 'org.gnome.Documents.desktop',
-        'gnome-font-viewer.desktop': 'org.gnome.font-viewer.desktop',
-        'gnome-maps.desktop': 'org.gnome.Maps.desktop',
-        'gnome-nibbles.desktop': 'org.gnome.Nibbles.desktop',
-        'gnome-photos.desktop': 'org.gnome.Photos.desktop',
-        'gnome-power-statistics.desktop': 'org.gnome.PowerStats.desktop',
-        'gnome-screenshot.desktop': 'org.gnome.Screenshot.desktop',
-        'gnome-software.desktop': 'org.gnome.Software.desktop',
-        'gnome-sound-recorder.desktop': 'org.gnome.SoundRecorder.desktop',
-        'gnome-terminal.desktop': 'org.gnome.Terminal.desktop',
-        'gnome-weather.desktop': 'org.gnome.Weather.Application.desktop',
-        'gnomine.desktop': 'gnome-mines.desktop',
-        'gnotravex.desktop': 'gnome-tetravex.desktop',
-        'gnotski.desktop': 'gnome-klotski.desktop',
-        'gtali.desktop': 'tali.desktop',
-        'hitori.desktop': 'org.gnome.Hitori.desktop',
-        'latexila.desktop': 'org.gnome.latexila.desktop',
-        'lollypop.desktop': 'org.gnome.Lollypop.desktop',
-        'nautilus.desktop': 'org.gnome.Nautilus.desktop',
-        'polari.desktop': 'org.gnome.Polari.desktop',
-        'sound-juicer.desktop': 'org.gnome.SoundJuicer.desktop',
-        'totem.desktop': 'org.gnome.Totem.desktop',
-        'akregator.desktop': 'org.kde.akregator.desktop',
-        'apper.desktop': 'org.kde.apper.desktop',
-        'ark.desktop': 'org.kde.ark.desktop',
-        'blinken.desktop': 'org.kde.blinken.desktop',
-        'cantor.desktop': 'org.kde.cantor.desktop',
-        'digikam.desktop': 'org.kde.digikam.desktop',
-        'dolphin.desktop': 'org.kde.dolphin.desktop',
-        'dragonplayer.desktop': 'org.kde.dragonplayer.desktop',
-        'filelight.desktop': 'org.kde.filelight.desktop',
-        'gwenview.desktop': 'org.kde.gwenview.desktop',
-        'juk.desktop': 'org.kde.juk.desktop',
-        'kajongg.desktop': 'org.kde.kajongg.desktop',
-        'kalgebra.desktop': 'org.kde.kalgebra.desktop',
-        'kalzium.desktop': 'org.kde.kalzium.desktop',
-        'kamoso.desktop': 'org.kde.kamoso.desktop',
-        'kanagram.desktop': 'org.kde.kanagram.desktop',
-        'kapman.desktop': 'org.kde.kapman.desktop',
-        'kapptemplate.desktop': 'org.kde.kapptemplate.desktop',
-        'kbruch.desktop': 'org.kde.kbruch.desktop',
-        'kdevelop.desktop': 'org.kde.kdevelop.desktop',
-        'kfind.desktop': 'org.kde.kfind.desktop',
-        'kgeography.desktop': 'org.kde.kgeography.desktop',
-        'kgpg.desktop': 'org.kde.kgpg.desktop',
-        'khangman.desktop': 'org.kde.khangman.desktop',
-        'kig.desktop': 'org.kde.kig.desktop',
-        'kiriki.desktop': 'org.kde.kiriki.desktop',
-        'kiten.desktop': 'org.kde.kiten.desktop',
-        'klettres.desktop': 'org.kde.klettres.desktop',
-        'klipper.desktop': 'org.kde.klipper.desktop',
-        'KMail2.desktop': 'org.kde.kmail.desktop',
-        'kmplot.desktop': 'org.kde.kmplot.desktop',
-        'kollision.desktop': 'org.kde.kollision.desktop',
-        'kolourpaint.desktop': 'org.kde.kolourpaint.desktop',
-        'konsole.desktop': 'org.kde.konsole.desktop',
-        'Kontact.desktop': 'org.kde.kontact.desktop',
-        'korganizer.desktop': 'org.kde.korganizer.desktop',
-        'krita.desktop': 'org.kde.krita.desktop',
-        'kshisen.desktop': 'org.kde.kshisen.desktop',
-        'kstars.desktop': 'org.kde.kstars.desktop',
-        'ksudoku.desktop': 'org.kde.ksudoku.desktop',
-        'ktouch.desktop': 'org.kde.ktouch.desktop',
-        'ktp-log-viewer.desktop': 'org.kde.ktplogviewer.desktop',
-        'kturtle.desktop': 'org.kde.kturtle.desktop',
-        'kwordquiz.desktop': 'org.kde.kwordquiz.desktop',
-        'marble.desktop': 'org.kde.marble.desktop',
-        'okteta.desktop': 'org.kde.okteta.desktop',
-        'parley.desktop': 'org.kde.parley.desktop',
-        'partitionmanager.desktop': 'org.kde.PartitionManager.desktop',
-        'picmi.desktop': 'org.kde.picmi.desktop',
-        'rocs.desktop': 'org.kde.rocs.desktop',
-        'showfoto.desktop': 'org.kde.showfoto.desktop',
-        'skrooge.desktop': 'org.kde.skrooge.desktop',
-        'step.desktop': 'org.kde.step.desktop',
-        'yakuake.desktop': 'org.kde.yakuake.desktop',
-        'colorhug-ccmx.desktop': 'com.hughski.ColorHug.CcmxLoader.desktop',
-        'colorhug-flash.desktop': 'com.hughski.ColorHug.FlashLoader.desktop',
-        'dconf-editor.desktop': 'ca.desrt.dconf-editor.desktop',
-        'feedreader.desktop': 'org.gnome.FeedReader.desktop',
-        'qtcreator.desktop': 'org.qt-project.qtcreator.desktop',
+        "baobab.desktop": "org.gnome.baobab.desktop",
+        "bijiben.desktop": "org.gnome.bijiben.desktop",
+        "cheese.desktop": "org.gnome.Cheese.desktop",
+        "devhelp.desktop": "org.gnome.Devhelp.desktop",
+        "epiphany.desktop": "org.gnome.Epiphany.desktop",
+        "file-roller.desktop": "org.gnome.FileRoller.desktop",
+        "font-manager.desktop": "org.gnome.FontManager.desktop",
+        "gcalctool.desktop": "gnome-calculator.desktop",
+        "gcm-viewer.desktop": "org.gnome.ColorProfileViewer.desktop",
+        "geary.desktop": "org.gnome.Geary.desktop",
+        "gedit.desktop": "org.gnome.gedit.desktop",
+        "glchess.desktop": "gnome-chess.desktop",
+        "glines.desktop": "five-or-more.desktop",
+        "gnect.desktop": "four-in-a-row.desktop",
+        "gnibbles.desktop": "gnome-nibbles.desktop",
+        "gnobots2.desktop": "gnome-robots.desktop",
+        "gnome-2048.desktop": "org.gnome.gnome-2048.desktop",
+        "gnome-boxes.desktop": "org.gnome.Boxes.desktop",
+        "gnome-calculator.desktop": "org.gnome.Calculator.desktop",
+        "gnome-clocks.desktop": "org.gnome.clocks.desktop",
+        "gnome-contacts.desktop": "org.gnome.Contacts.desktop",
+        "gnome-dictionary.desktop": "org.gnome.Dictionary.desktop",
+        "gnome-disks.desktop": "org.gnome.DiskUtility.desktop",
+        "gnome-documents.desktop": "org.gnome.Documents.desktop",
+        "gnome-font-viewer.desktop": "org.gnome.font-viewer.desktop",
+        "gnome-maps.desktop": "org.gnome.Maps.desktop",
+        "gnome-nibbles.desktop": "org.gnome.Nibbles.desktop",
+        "gnome-photos.desktop": "org.gnome.Photos.desktop",
+        "gnome-power-statistics.desktop": "org.gnome.PowerStats.desktop",
+        "gnome-screenshot.desktop": "org.gnome.Screenshot.desktop",
+        "gnome-software.desktop": "org.gnome.Software.desktop",
+        "gnome-sound-recorder.desktop": "org.gnome.SoundRecorder.desktop",
+        "gnome-terminal.desktop": "org.gnome.Terminal.desktop",
+        "gnome-weather.desktop": "org.gnome.Weather.Application.desktop",
+        "gnomine.desktop": "gnome-mines.desktop",
+        "gnotravex.desktop": "gnome-tetravex.desktop",
+        "gnotski.desktop": "gnome-klotski.desktop",
+        "gtali.desktop": "tali.desktop",
+        "hitori.desktop": "org.gnome.Hitori.desktop",
+        "latexila.desktop": "org.gnome.latexila.desktop",
+        "lollypop.desktop": "org.gnome.Lollypop.desktop",
+        "nautilus.desktop": "org.gnome.Nautilus.desktop",
+        "polari.desktop": "org.gnome.Polari.desktop",
+        "sound-juicer.desktop": "org.gnome.SoundJuicer.desktop",
+        "totem.desktop": "org.gnome.Totem.desktop",
+        "akregator.desktop": "org.kde.akregator.desktop",
+        "apper.desktop": "org.kde.apper.desktop",
+        "ark.desktop": "org.kde.ark.desktop",
+        "blinken.desktop": "org.kde.blinken.desktop",
+        "cantor.desktop": "org.kde.cantor.desktop",
+        "digikam.desktop": "org.kde.digikam.desktop",
+        "dolphin.desktop": "org.kde.dolphin.desktop",
+        "dragonplayer.desktop": "org.kde.dragonplayer.desktop",
+        "filelight.desktop": "org.kde.filelight.desktop",
+        "gwenview.desktop": "org.kde.gwenview.desktop",
+        "juk.desktop": "org.kde.juk.desktop",
+        "kajongg.desktop": "org.kde.kajongg.desktop",
+        "kalgebra.desktop": "org.kde.kalgebra.desktop",
+        "kalzium.desktop": "org.kde.kalzium.desktop",
+        "kamoso.desktop": "org.kde.kamoso.desktop",
+        "kanagram.desktop": "org.kde.kanagram.desktop",
+        "kapman.desktop": "org.kde.kapman.desktop",
+        "kapptemplate.desktop": "org.kde.kapptemplate.desktop",
+        "kbruch.desktop": "org.kde.kbruch.desktop",
+        "kdevelop.desktop": "org.kde.kdevelop.desktop",
+        "kfind.desktop": "org.kde.kfind.desktop",
+        "kgeography.desktop": "org.kde.kgeography.desktop",
+        "kgpg.desktop": "org.kde.kgpg.desktop",
+        "khangman.desktop": "org.kde.khangman.desktop",
+        "kig.desktop": "org.kde.kig.desktop",
+        "kiriki.desktop": "org.kde.kiriki.desktop",
+        "kiten.desktop": "org.kde.kiten.desktop",
+        "klettres.desktop": "org.kde.klettres.desktop",
+        "klipper.desktop": "org.kde.klipper.desktop",
+        "KMail2.desktop": "org.kde.kmail.desktop",
+        "kmplot.desktop": "org.kde.kmplot.desktop",
+        "kollision.desktop": "org.kde.kollision.desktop",
+        "kolourpaint.desktop": "org.kde.kolourpaint.desktop",
+        "konsole.desktop": "org.kde.konsole.desktop",
+        "Kontact.desktop": "org.kde.kontact.desktop",
+        "korganizer.desktop": "org.kde.korganizer.desktop",
+        "krita.desktop": "org.kde.krita.desktop",
+        "kshisen.desktop": "org.kde.kshisen.desktop",
+        "kstars.desktop": "org.kde.kstars.desktop",
+        "ksudoku.desktop": "org.kde.ksudoku.desktop",
+        "ktouch.desktop": "org.kde.ktouch.desktop",
+        "ktp-log-viewer.desktop": "org.kde.ktplogviewer.desktop",
+        "kturtle.desktop": "org.kde.kturtle.desktop",
+        "kwordquiz.desktop": "org.kde.kwordquiz.desktop",
+        "marble.desktop": "org.kde.marble.desktop",
+        "okteta.desktop": "org.kde.okteta.desktop",
+        "parley.desktop": "org.kde.parley.desktop",
+        "partitionmanager.desktop": "org.kde.PartitionManager.desktop",
+        "picmi.desktop": "org.kde.picmi.desktop",
+        "rocs.desktop": "org.kde.rocs.desktop",
+        "showfoto.desktop": "org.kde.showfoto.desktop",
+        "skrooge.desktop": "org.kde.skrooge.desktop",
+        "step.desktop": "org.kde.step.desktop",
+        "yakuake.desktop": "org.kde.yakuake.desktop",
+        "colorhug-ccmx.desktop": "com.hughski.ColorHug.CcmxLoader.desktop",
+        "colorhug-flash.desktop": "com.hughski.ColorHug.FlashLoader.desktop",
+        "dconf-editor.desktop": "ca.desrt.dconf-editor.desktop",
+        "feedreader.desktop": "org.gnome.FeedReader.desktop",
+        "qtcreator.desktop": "org.qt-project.qtcreator.desktop",
     }
     for app_id in mapping:
         if not app_id in components:
@@ -137,12 +141,16 @@ def upgrade():
             continue
         if components[app_id].component_id_parent:
             continue
-        print('adding legacy parent for {} -> {}'.format(components[app_id].app_id,
-                                                         components[app_id_new].app_id))
+        print(
+            "adding legacy parent for {} -> {}".format(
+                components[app_id].app_id, components[app_id_new].app_id
+            )
+        )
         components[app_id_new].adopt(components[app_id])
 
     # done
     db.session.commit()
 
+
 def downgrade():
     pass
diff --git a/app_data/migrations/versions/f32bd8265c3b_.py b/app_data/migrations/versions/f32bd8265c3b_.py
index 3e8725e..1236d5d 100644
--- a/app_data/migrations/versions/f32bd8265c3b_.py
+++ b/app_data/migrations/versions/f32bd8265c3b_.py
@@ -7,18 +7,27 @@ Create Date: 2019-07-04 16:35:39.673744
 """
 
 # revision identifiers, used by Alembic.
-revision = 'f32bd8265c3b'
-down_revision = 'a22c286d8094'
+revision = "f32bd8265c3b"
+down_revision = "a22c286d8094"
 
 from alembic import op
 import sqlalchemy as sa
 from sqlalchemy.dialects import mysql
 
+
 def upgrade():
-    op.add_column('components', sa.Column('component_id_parent', sa.Integer(), nullable=True))
-    op.create_foreign_key('components_ibfk_4', 'components', 'components', ['component_id_parent'], 
['component_id'])
+    op.add_column(
+        "components", sa.Column("component_id_parent", sa.Integer(), nullable=True)
+    )
+    op.create_foreign_key(
+        "components_ibfk_4",
+        "components",
+        "components",
+        ["component_id_parent"],
+        ["component_id"],
+    )
 
 
 def downgrade():
-    op.drop_constraint('components_ibfk_4', 'components', type_='foreignkey')
-    op.drop_column('components', 'component_id_parent')
+    op.drop_constraint("components_ibfk_4", "components", type_="foreignkey")
+    op.drop_column("components", "component_id_parent")
diff --git a/app_data/odrs/__init__.py b/app_data/odrs/__init__.py
index 05009ee..06f514a 100644
--- a/app_data/odrs/__init__.py
+++ b/app_data/odrs/__init__.py
@@ -21,12 +21,11 @@ from .dbutils import drop_db, init_db
 
 app = Flask(__name__)
 app.config.from_object(__name__)
-if 'ODRS_CONFIG' in os.environ:
-    app.config.from_envvar('ODRS_CONFIG')
-if 'ODRS_REVIEWS_SECRET' in os.environ:
-    app.secret_key = os.environ['ODRS_REVIEWS_SECRET']
-for key in ['SQLALCHEMY_DATABASE_URI',
-            'SQLALCHEMY_TRACK_MODIFICATIONS']:
+if "ODRS_CONFIG" in os.environ:
+    app.config.from_envvar("ODRS_CONFIG")
+if "ODRS_REVIEWS_SECRET" in os.environ:
+    app.secret_key = os.environ["ODRS_REVIEWS_SECRET"]
+for key in ["SQLALCHEMY_DATABASE_URI", "SQLALCHEMY_TRACK_MODIFICATIONS"]:
     if key in os.environ:
         app.config[key] = os.environ[key]
 
@@ -36,37 +35,48 @@ migrate = Migrate(app, db)
 
 csrf = CSRFProtect(app)
 
-@app.cli.command('initdb')
+
+@app.cli.command("initdb")
 def initdb_command():
     init_db(db)
 
-@app.cli.command('dropdb')
+
+@app.cli.command("dropdb")
 def dropdb_command():
     drop_db(db)
 
+
 lm = LoginManager(app)
-lm.login_view = 'odrs_login'
+lm.login_view = "odrs_login"
+
 
 @app.teardown_appcontext
 def shutdown_session(unused_exception=None):
     db.session.remove()
 
+
 @lm.user_loader
 def load_user(user_id):
     from .models import Moderator
-    g.user = db.session.query(Moderator).filter(Moderator.moderator_id == user_id).first()
+
+    g.user = (
+        db.session.query(Moderator).filter(Moderator.moderator_id == user_id).first()
+    )
     return g.user
 
+
 @app.errorhandler(404)
 def error_page_not_found(msg=None):
-    """ Error handler: File not found """
+    """Error handler: File not found"""
     flash(msg)
-    return render_template('error.html'), 404
+    return render_template("error.html"), 404
+
 
 @app.errorhandler(CSRFError)
 def error_csrf(e):
-    flash(str(e), 'danger')
-    return redirect(url_for('.odrs_index'))
+    flash(str(e), "danger")
+    return redirect(url_for(".odrs_index"))
+
 
 from odrs import views
 from odrs import views_api
diff --git a/app_data/odrs/dbutils.py b/app_data/odrs/dbutils.py
index 6e3934e..d608d6d 100644
--- a/app_data/odrs/dbutils.py
+++ b/app_data/odrs/dbutils.py
@@ -7,6 +7,7 @@
 #
 # SPDX-License-Identifier: GPL-3.0+
 
+
 def init_db(db):
 
     # ensure all tables exist
@@ -14,18 +15,28 @@ def init_db(db):
 
     # ensure admin user exists
     from .models import Moderator, User
-    user = db.session.query(User).filter(User.user_hash == 
'deadbeef348c0f88529f3bfd937ec1a5d90aefc7').first()
+
+    user = (
+        db.session.query(User)
+        .filter(User.user_hash == "deadbeef348c0f88529f3bfd937ec1a5d90aefc7")
+        .first()
+    )
     if not user:
-        user = User('deadbeef348c0f88529f3bfd937ec1a5d90aefc7')
+        user = User("deadbeef348c0f88529f3bfd937ec1a5d90aefc7")
         db.session.add(user)
         db.session.commit()
-    if not db.session.query(Moderator).filter(Moderator.username == 'admin test com').first():
-        mod = Moderator(username='admin test com')
-        mod.password = 'Pa$$w0rd'
+    if (
+        not db.session.query(Moderator)
+        .filter(Moderator.username == "admin test com")
+        .first()
+    ):
+        mod = Moderator(username="admin test com")
+        mod.password = "Pa$$w0rd"
         mod.is_admin = True
         mod.user_id = user.user_id
         db.session.add(mod)
         db.session.commit()
 
+
 def drop_db(db):
     db.metadata.drop_all(bind=db.engine)
diff --git a/app_data/odrs/models.py b/app_data/odrs/models.py
index 10662bf..edbfb50 100644
--- a/app_data/odrs/models.py
+++ b/app_data/odrs/models.py
@@ -12,27 +12,41 @@ import re
 
 from werkzeug.security import generate_password_hash, check_password_hash
 
-from sqlalchemy import Column, Integer, String, Text, DateTime, Boolean, Index, ForeignKey
+from sqlalchemy import (
+    Column,
+    Integer,
+    String,
+    Text,
+    DateTime,
+    Boolean,
+    Index,
+    ForeignKey,
+)
 from sqlalchemy.orm import relationship
 
 from odrs import db
 
 from .util import _password_hash, _get_user_key, _addr_hash
 
+
 def _vote_exists(review_id, user_id):
-    """ Checks to see if a vote exists for the review+user """
-    return db.session.query(Vote).\
-                filter(Vote.review_id == review_id).\
-                filter(Vote.user_id == user_id).\
-                first()
+    """Checks to see if a vote exists for the review+user"""
+    return (
+        db.session.query(Vote)
+        .filter(Vote.review_id == review_id)
+        .filter(Vote.user_id == user_id)
+        .first()
+    )
+
 
 class Analytic(db.Model):
 
     # sqlalchemy metadata
-    __tablename__ = 'analytics'
-    __table_args__ = (Index('datestr', 'datestr', 'app_id', unique=True),
-                      {'mysql_character_set': 'utf8mb4'}
-                     )
+    __tablename__ = "analytics"
+    __table_args__ = (
+        Index("datestr", "datestr", "app_id", unique=True),
+        {"mysql_character_set": "utf8mb4"},
+    )
 
     datestr = Column(Integer, default=0, primary_key=True)
     app_id = Column(String(128), primary_key=True)
@@ -42,13 +56,14 @@ class Analytic(db.Model):
         self.datestr = None
 
     def __repr__(self):
-        return 'Analytic object %s' % self.analytic_id
+        return "Analytic object %s" % self.analytic_id
+
 
 class Taboo(db.Model):
 
     # sqlalchemy metadata
-    __tablename__ = 'taboos'
-    __table_args__ = {'mysql_character_set': 'utf8mb4'}
+    __tablename__ = "taboos"
+    __table_args__ = {"mysql_character_set": "utf8mb4"}
 
     taboo_id = Column(Integer, primary_key=True, nullable=False, unique=True)
     locale = Column(String(8), nullable=False, index=True)
@@ -63,38 +78,39 @@ class Taboo(db.Model):
         self.severity = severity
 
     def asdict(self):
-        item = {'value': self.value}
+        item = {"value": self.value}
         if self.severity:
-            item['severity'] = self.severity
+            item["severity"] = self.severity
         if self.description:
-            item['description'] = self.description
+            item["description"] = self.description
         return item
 
     @property
     def color(self):
         if self.severity == 3:
-            return 'danger'
+            return "danger"
         if self.severity == 2:
-            return 'warning'
-        return 'info'
+            return "warning"
+        return "info"
 
     def __repr__(self):
-        return 'Taboo object %s' % self.taboo_id
+        return "Taboo object %s" % self.taboo_id
+
 
 class Vote(db.Model):
 
     # sqlalchemy metadata
-    __tablename__ = 'votes'
-    __table_args__ = {'mysql_character_set': 'utf8mb4'}
+    __tablename__ = "votes"
+    __table_args__ = {"mysql_character_set": "utf8mb4"}
 
     vote_id = Column(Integer, primary_key=True, nullable=False, unique=True)
     date_created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
-    review_id = Column(Integer, ForeignKey('reviews.review_id'), nullable=True)
-    user_id = Column(Integer, ForeignKey('users.user_id'), nullable=True)
+    review_id = Column(Integer, ForeignKey("reviews.review_id"), nullable=True)
+    user_id = Column(Integer, ForeignKey("users.user_id"), nullable=True)
     val = Column(Integer, default=0)
 
-    user = relationship('User')
-    review = relationship('Review')
+    user = relationship("User")
+    review = relationship("Review")
 
     def __init__(self, user_id, val, review_id=0):
         self.review_id = review_id
@@ -102,16 +118,18 @@ class Vote(db.Model):
         self.val = val
 
     def __repr__(self):
-        return 'Vote object %s' % self.vote_id
+        return "Vote object %s" % self.vote_id
+
 
 class User(db.Model):
 
     # sqlalchemy metadata
-    __tablename__ = 'users'
-    __table_args__ = {'mysql_character_set': 'utf8mb4'}
-    __table_args__ = (Index('users_hash_idx', 'user_hash'),
-                      {'mysql_character_set': 'utf8mb4'}
-                     )
+    __tablename__ = "users"
+    __table_args__ = {"mysql_character_set": "utf8mb4"}
+    __table_args__ = (
+        Index("users_hash_idx", "user_hash"),
+        {"mysql_character_set": "utf8mb4"},
+    )
 
     user_id = Column(Integer, primary_key=True, nullable=False, unique=True)
     date_created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
@@ -119,9 +137,7 @@ class User(db.Model):
     karma = Column(Integer, default=0)
     is_banned = Column(Boolean, default=False)
 
-    reviews = relationship('Review',
-                           back_populates='user',
-                           cascade='all,delete-orphan')
+    reviews = relationship("Review", back_populates="user", cascade="all,delete-orphan")
 
     def __init__(self, user_hash=None):
         self.user_hash = user_hash
@@ -129,31 +145,35 @@ class User(db.Model):
         self.is_banned = False
 
     def __repr__(self):
-        return 'User object %s' % self.user_id
+        return "User object %s" % self.user_id
+
 
 def _tokenize(val):
     return [token.lower() for token in re.findall(r"[\w']+", val)]
 
+
 class Component(db.Model):
 
     # sqlalchemy metadata
-    __tablename__ = 'components'
-    __table_args__ = {'mysql_character_set': 'utf8mb4'}
+    __tablename__ = "components"
+    __table_args__ = {"mysql_character_set": "utf8mb4"}
 
     component_id = Column(Integer, primary_key=True, nullable=False, unique=True)
-    component_id_parent = Column(Integer, ForeignKey('components.component_id'))
+    component_id_parent = Column(Integer, ForeignKey("components.component_id"))
     app_id = Column(Text)
     fetch_cnt = Column(Integer, default=0)
     review_cnt = Column(Integer, default=1)
 
-    reviews = relationship('Review',
-                           back_populates='component',
-                           cascade='all,delete-orphan')
-    parent = relationship('Component',
-                          uselist=False,
-                          remote_side='Component.component_id',
-                          backref='children',
-                          lazy='joined')
+    reviews = relationship(
+        "Review", back_populates="component", cascade="all,delete-orphan"
+    )
+    parent = relationship(
+        "Component",
+        uselist=False,
+        remote_side="Component.component_id",
+        backref="children",
+        lazy="joined",
+    )
 
     def __init__(self, app_id):
         self.app_id = app_id
@@ -185,23 +205,26 @@ class Component(db.Model):
         return app_ids
 
     def __repr__(self):
-        return 'Component object %s' % self.component_id
+        return "Component object %s" % self.component_id
+
 
 class Review(db.Model):
 
     # sqlalchemy metadata
-    __tablename__ = 'reviews'
-    __table_args__ = {'mysql_character_set': 'utf8mb4'}
+    __tablename__ = "reviews"
+    __table_args__ = {"mysql_character_set": "utf8mb4"}
 
     review_id = Column(Integer, primary_key=True, nullable=False, unique=True)
     date_created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
     date_deleted = Column(DateTime)
-    component_id = Column(Integer, ForeignKey('components.component_id'), nullable=False)
+    component_id = Column(
+        Integer, ForeignKey("components.component_id"), nullable=False
+    )
     locale = Column(Text)
     summary = Column(Text)
     description = Column(Text)
-    user_id = Column(Integer, ForeignKey('users.user_id'), nullable=True)
-    user_addr_hash = Column('user_addr', Text)
+    user_id = Column(Integer, ForeignKey("users.user_id"), nullable=True)
+    user_addr_hash = Column("user_addr", Text)
     user_display = Column(Text)
     version = Column(Text)
     distro = Column(Text)
@@ -210,13 +233,13 @@ class Review(db.Model):
     karma_down = Column(Integer, default=0)
     reported = Column(Integer, default=0)
 
-    user = relationship('User', back_populates='reviews')
-    component = relationship('Component',   # the one used for submit()
-                             back_populates='reviews',
-                             lazy='joined')
-    votes = relationship('Vote',
-                         back_populates='review',
-                         cascade='all,delete-orphan')
+    user = relationship("User", back_populates="reviews")
+    component = relationship(
+        "Component",  # the one used for submit()
+        back_populates="reviews",
+        lazy="joined",
+    )
+    votes = relationship("Vote", back_populates="review", cascade="all,delete-orphan")
 
     def __init__(self):
         self.locale = None
@@ -266,7 +289,7 @@ class Review(db.Model):
 
     @property
     def user_addr(self):
-        raise AttributeError('user_addr is not a readable attribute')
+        raise AttributeError("user_addr is not a readable attribute")
 
     @user_addr.setter
     def user_addr(self, user_addr):
@@ -274,49 +297,53 @@ class Review(db.Model):
 
     def asdict(self, user_hash=None):
         item = {
-            'app_id': self.component.app_id,
-            'date_created': self.date_created.timestamp(),
-            'description': self.description,
-            'distro': self.distro,
-            'karma_down': self.karma_down,
-            'karma_up': self.karma_up,
-            'locale': self.locale,
-            'rating': self.rating,
-            'reported': self.reported,
-            'review_id': self.review_id,
-            'summary': self.summary,
-            'user_display': self.user_display,
-            'version': self.version,
+            "app_id": self.component.app_id,
+            "date_created": self.date_created.timestamp(),
+            "description": self.description,
+            "distro": self.distro,
+            "karma_down": self.karma_down,
+            "karma_up": self.karma_up,
+            "locale": self.locale,
+            "rating": self.rating,
+            "reported": self.reported,
+            "review_id": self.review_id,
+            "summary": self.summary,
+            "user_display": self.user_display,
+            "version": self.version,
         }
         if self.user:
-            item['user_hash'] = self.user.user_hash
+            item["user_hash"] = self.user.user_hash
         if user_hash:
-            item['user_skey'] = _get_user_key(user_hash, self.component.app_id)
+            item["user_skey"] = _get_user_key(user_hash, self.component.app_id)
         return item
 
     def __repr__(self):
-        return 'Review object %s' % self.review_id
+        return "Review object %s" % self.review_id
+
 
 class Event(db.Model):
 
     # sqlalchemy metadata
-    __tablename__ = 'eventlog'
-    __table_args__ = (Index('message_idx', 'message', mysql_length=8),
-                      Index('date_created_idx', 'date_created'),
-                      {'mysql_character_set': 'utf8mb4'}
-                     )
+    __tablename__ = "eventlog"
+    __table_args__ = (
+        Index("message_idx", "message", mysql_length=8),
+        Index("date_created_idx", "date_created"),
+        {"mysql_character_set": "utf8mb4"},
+    )
 
     eventlog_id = Column(Integer, primary_key=True, nullable=False, unique=True)
     date_created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
     user_addr = Column(Text)
-    user_id = Column(Integer, ForeignKey('users.user_id'), nullable=True)
+    user_id = Column(Integer, ForeignKey("users.user_id"), nullable=True)
     message = Column(Text)
     app_id = Column(Text)
     important = Column(Boolean, default=False)
 
-    user = relationship('User')
+    user = relationship("User")
 
-    def __init__(self, user_addr, user_id=None, app_id=None, message=None, important=False):
+    def __init__(
+        self, user_addr, user_id=None, app_id=None, message=None, important=False
+    ):
         self.user_addr = user_addr
         self.user_id = user_id
         self.message = message
@@ -324,24 +351,25 @@ class Event(db.Model):
         self.important = important
 
     def __repr__(self):
-        return 'Event object %s' % self.eventlog_id
+        return "Event object %s" % self.eventlog_id
+
 
 class Moderator(db.Model):
 
     # sqlalchemy metadata
-    __tablename__ = 'moderators'
-    __table_args__ = {'mysql_character_set': 'utf8mb4'}
+    __tablename__ = "moderators"
+    __table_args__ = {"mysql_character_set": "utf8mb4"}
 
     moderator_id = Column(Integer, primary_key=True, nullable=False, unique=True)
     username = Column(Text)
-    password_hash = Column('password', Text)
+    password_hash = Column("password", Text)
     display_name = Column(Text)
     is_enabled = Column(Boolean, default=False)
     is_admin = Column(Boolean, default=False)
-    user_id = Column(Integer, ForeignKey('users.user_id'), nullable=True)
+    user_id = Column(Integer, ForeignKey("users.user_id"), nullable=True)
     locales = Column(Text)
 
-    user = relationship('User')
+    user = relationship("User")
 
     def __init__(self, username=None, password=None, display_name=None):
         self.username = username
@@ -354,7 +382,7 @@ class Moderator(db.Model):
 
     @property
     def password(self):
-        raise AttributeError('password is not a readable attribute')
+        raise AttributeError("password is not a readable attribute")
 
     @password.setter
     def password(self, password):
@@ -387,4 +415,4 @@ class Moderator(db.Model):
         return str(self.moderator_id)
 
     def __repr__(self):
-        return 'Moderator object %s' % self.moderator_id
+        return "Moderator object %s" % self.moderator_id
diff --git a/app_data/odrs/tests/odrs_test.py b/app_data/odrs/tests/odrs_test.py
index 0cafe39..3b96b5b 100644
--- a/app_data/odrs/tests/odrs_test.py
+++ b/app_data/odrs/tests/odrs_test.py
@@ -15,35 +15,40 @@ import unittest
 import tempfile
 
 # allows us to run this from the project root
-sys.path.append(os.path.realpath('.'))
+sys.path.append(os.path.realpath("."))
 
 from odrs.util import _get_user_key
 
-class OdrsTest(unittest.TestCase):
 
+class OdrsTest(unittest.TestCase):
     def setUp(self):
 
         # create new database
         self.db_fd, self.db_filename = tempfile.mkstemp()
-        self.db_uri = 'sqlite:///' + self.db_filename
-        self.user_hash = 'deadbeef348c0f88529f3bfd937ec1a5d90aefc7'
+        self.db_uri = "sqlite:///" + self.db_filename
+        self.user_hash = "deadbeef348c0f88529f3bfd937ec1a5d90aefc7"
 
         # write out custom settings file
         self.cfg_fd, self.cfg_filename = tempfile.mkstemp()
-        with open(self.cfg_filename, 'w') as cfgfile:
-            cfgfile.write('\n'.join([
-                "SQLALCHEMY_DATABASE_URI = '%s'" % self.db_uri,
-                "SQLALCHEMY_TRACK_MODIFICATIONS = False",
-                "SECRET_KEY = 'not-secret4'",
-                "ODRS_REVIEWS_SECRET = '1'",
-                "WTF_CSRF_CHECK_DEFAULT = False",
-                "DEBUG = True",
-                ]))
+        with open(self.cfg_filename, "w") as cfgfile:
+            cfgfile.write(
+                "\n".join(
+                    [
+                        "SQLALCHEMY_DATABASE_URI = '%s'" % self.db_uri,
+                        "SQLALCHEMY_TRACK_MODIFICATIONS = False",
+                        "SECRET_KEY = 'not-secret4'",
+                        "ODRS_REVIEWS_SECRET = '1'",
+                        "WTF_CSRF_CHECK_DEFAULT = False",
+                        "DEBUG = True",
+                    ]
+                )
+            )
 
         # create instance
         import odrs
         from odrs import db
         from odrs.dbutils import init_db
+
         self.app = odrs.app.test_client()
         odrs.app.config.from_pyfile(self.cfg_filename)
         with odrs.app.app_context():
@@ -51,13 +56,17 @@ class OdrsTest(unittest.TestCase):
 
         # assign user_hash to this account
         self.login()
-        rv = self.app.post('/admin/moderator/1/modify_by_admin', data=dict(
-            is_enabled=True,
-            is_admin=True,
-            locales='en',
-            user_hash=self.user_hash,
-        ), follow_redirects=True)
-        assert b'Updated profile' in rv.data, rv.data
+        rv = self.app.post(
+            "/admin/moderator/1/modify_by_admin",
+            data=dict(
+                is_enabled=True,
+                is_admin=True,
+                locales="en",
+                user_hash=self.user_hash,
+            ),
+            follow_redirects=True,
+        )
+        assert b"Updated profile" in rv.data, rv.data
         self.logout()
 
     def tearDown(self):
@@ -66,32 +75,33 @@ class OdrsTest(unittest.TestCase):
         os.close(self.cfg_fd)
         os.unlink(self.cfg_filename)
 
-    def _login(self, username, password='Pa$$w0rd'):
-        return self.app.post('/login', data=dict(
-            username=username,
-            password=password
-        ), follow_redirects=True)
+    def _login(self, username, password="Pa$$w0rd"):
+        return self.app.post(
+            "/login",
+            data=dict(username=username, password=password),
+            follow_redirects=True,
+        )
 
     def _logout(self):
-        return self.app.get('/logout', follow_redirects=True)
+        return self.app.get("/logout", follow_redirects=True)
 
-    def login(self, username='admin test com', password='Pa$$w0rd'):
+    def login(self, username="admin test com", password="Pa$$w0rd"):
         rv = self._login(username, password)
-        assert b'Logged in' in rv.data, rv.data
-        assert b'/admin/show/reported' in rv.data, rv.data
-        assert b'Incorrect username' not in rv.data, rv.data
+        assert b"Logged in" in rv.data, rv.data
+        assert b"/admin/show/reported" in rv.data, rv.data
+        assert b"Incorrect username" not in rv.data, rv.data
 
     def logout(self):
         rv = self._logout()
-        assert b'Logged out' in rv.data, rv.data
-        assert b'/admin/show/reported' not in rv.data, rv.data
+        assert b"Logged out" in rv.data, rv.data
+        assert b"/admin/show/reported" not in rv.data, rv.data
 
     def test_admin_show_review_for_app(self):
 
         self.review_submit()
         self.login()
-        rv = self.app.get('/admin/show/app/inkscape.desktop')
-        assert b'n essential part of my daily' in rv.data, rv.data
+        rv = self.app.get("/admin/show/app/inkscape.desktop")
+        assert b"n essential part of my daily" in rv.data, rv.data
 
     def test_admin_graphs(self):
 
@@ -101,18 +111,18 @@ class OdrsTest(unittest.TestCase):
         self.review_fetch()
 
         self.login()
-        rv = self.app.get('/admin/graph_month')
-        assert b'Chart.js' in rv.data, rv.data
-        assert b'0, 1' in rv.data, rv.data
+        rv = self.app.get("/admin/graph_month")
+        assert b"Chart.js" in rv.data, rv.data
+        assert b"0, 1" in rv.data, rv.data
 
-        rv = self.app.get('/admin/graph_year')
-        assert b'Chart.js' in rv.data, rv.data
-        assert b'0, 1' in rv.data, rv.data
+        rv = self.app.get("/admin/graph_year")
+        assert b"Chart.js" in rv.data, rv.data
+        assert b"0, 1" in rv.data, rv.data
 
-        rv = self.app.get('/admin/stats')
-        assert b'Chart.js' in rv.data, rv.data
-        assert b'Active reviews</td>\n    <td>1</td>' in rv.data, rv.data
-        assert b'Haters Gonna Hate' in rv.data, rv.data
+        rv = self.app.get("/admin/stats")
+        assert b"Chart.js" in rv.data, rv.data
+        assert b"Active reviews</td>\n    <td>1</td>" in rv.data, rv.data
+        assert b"Haters Gonna Hate" in rv.data, rv.data
 
     def test_admin_unreport(self):
 
@@ -120,186 +130,222 @@ class OdrsTest(unittest.TestCase):
         self.review_report()
 
         self.login()
-        rv = self.app.get('/admin/unreport/1', follow_redirects=True)
-        assert b'Review unreported' in rv.data, rv.data
+        rv = self.app.get("/admin/unreport/1", follow_redirects=True)
+        assert b"Review unreported" in rv.data, rv.data
 
     def test_admin_review(self):
 
-        rv = self._review_submit(locale='in_IN')
+        rv = self._review_submit(locale="in_IN")
         assert b'"success": true' in rv.data, rv.data
 
         self.login()
-        rv = self.app.get('/admin/review/1')
-        assert b'Inkscape has been a essential part of my workflow for many years' in rv.data, rv.data
-        assert b'Somebody Important' in rv.data, rv.data
-        assert b'Fedora' in rv.data, rv.data
-        rv = self.app.post('/admin/modify/1', data=dict(
-            distro='Ubuntu',
-        ), follow_redirects=True)
-        assert b'Inkscape has been a essential part of my workflow for many years' in rv.data, rv.data
-        assert b'Ubuntu' in rv.data, rv.data
-
-        rv = self.app.get('/admin/englishify/1', follow_redirects=True)
-        assert b'en_IN' in rv.data, rv.data
-
-        rv = self.app.get('/admin/anonify/1', follow_redirects=True)
-        assert b'Somebody Important' not in rv.data, rv.data
-
-        rv = self.app.get('/admin/vote/1/down', follow_redirects=True)
-        assert b'Recorded vote' in rv.data, rv.data
+        rv = self.app.get("/admin/review/1")
+        assert (
+            b"Inkscape has been a essential part of my workflow for many years"
+            in rv.data
+        ), rv.data
+        assert b"Somebody Important" in rv.data, rv.data
+        assert b"Fedora" in rv.data, rv.data
+        rv = self.app.post(
+            "/admin/modify/1",
+            data=dict(
+                distro="Ubuntu",
+            ),
+            follow_redirects=True,
+        )
+        assert (
+            b"Inkscape has been a essential part of my workflow for many years"
+            in rv.data
+        ), rv.data
+        assert b"Ubuntu" in rv.data, rv.data
+
+        rv = self.app.get("/admin/englishify/1", follow_redirects=True)
+        assert b"en_IN" in rv.data, rv.data
+
+        rv = self.app.get("/admin/anonify/1", follow_redirects=True)
+        assert b"Somebody Important" not in rv.data, rv.data
+
+        rv = self.app.get("/admin/vote/1/down", follow_redirects=True)
+        assert b"Recorded vote" in rv.data, rv.data
 
         # delete
-        rv = self.app.get('/admin/delete/1', follow_redirects=True)
-        assert b'Confirm Removal?' in rv.data, rv.data
-        rv = self.app.get('/admin/delete/1/force', follow_redirects=True)
-        assert b'Deleted review' in rv.data, rv.data
-        rv = self.app.get('/admin/review/1', follow_redirects=True)
-        assert b'No review with that ID' in rv.data, rv.data
-
-    def _admin_moderator_add(self, username='dave dave com', password='foobarbaz123.'):
-
-        return self.app.post('/admin/moderator/add', data=dict(
-            password_new=password,
-            username_new=username,
-            display_name='Dave',
-        ), follow_redirects=True)
+        rv = self.app.get("/admin/delete/1", follow_redirects=True)
+        assert b"Confirm Removal?" in rv.data, rv.data
+        rv = self.app.get("/admin/delete/1/force", follow_redirects=True)
+        assert b"Deleted review" in rv.data, rv.data
+        rv = self.app.get("/admin/review/1", follow_redirects=True)
+        assert b"No review with that ID" in rv.data, rv.data
+
+    def _admin_moderator_add(self, username="dave dave com", password="foobarbaz123."):
+
+        return self.app.post(
+            "/admin/moderator/add",
+            data=dict(
+                password_new=password,
+                username_new=username,
+                display_name="Dave",
+            ),
+            follow_redirects=True,
+        )
 
     def test_admin_add_moderator(self):
 
         self.login()
 
         # bad values
-        rv = self._admin_moderator_add(username='1')
-        assert b'Username invalid' in rv.data, rv.data
-        rv = self._admin_moderator_add(password='foo')
-        assert b'The password is too short' in rv.data, rv.data
-        rv = self._admin_moderator_add(password='foobarbaz')
-        assert b'requires at least one non-alphanumeric' in rv.data, rv.data
-        rv = self._admin_moderator_add(username='foo')
-        assert b'Invalid email address' in rv.data, rv.data
+        rv = self._admin_moderator_add(username="1")
+        assert b"Username invalid" in rv.data, rv.data
+        rv = self._admin_moderator_add(password="foo")
+        assert b"The password is too short" in rv.data, rv.data
+        rv = self._admin_moderator_add(password="foobarbaz")
+        assert b"requires at least one non-alphanumeric" in rv.data, rv.data
+        rv = self._admin_moderator_add(username="foo")
+        assert b"Invalid email address" in rv.data, rv.data
 
         # good values
         rv = self._admin_moderator_add()
-        assert b'Added user' in rv.data, rv.data
+        assert b"Added user" in rv.data, rv.data
 
         # duplicate
         rv = self._admin_moderator_add()
-        assert b'Already a entry with that username' in rv.data, rv.data
+        assert b"Already a entry with that username" in rv.data, rv.data
         self.logout()
 
         # test this actually works
-        self.login(username='dave dave com', password='foobarbaz123.')
+        self.login(username="dave dave com", password="foobarbaz123.")
         self.logout()
 
         # remove
         self.login()
-        rv = self.app.get('admin/moderator/2/delete', follow_redirects=True)
-        assert b'Deleted user' in rv.data, rv.data
+        rv = self.app.get("admin/moderator/2/delete", follow_redirects=True)
+        assert b"Deleted user" in rv.data, rv.data
 
     def test_admin_show_reviews(self):
 
         self.review_submit()
         self.login()
-        rv = self.app.get('/admin/show/all')
-        assert b'An essential ' in rv.data, rv.data
+        rv = self.app.get("/admin/show/all")
+        assert b"An essential " in rv.data, rv.data
 
-        rv = self.app.get('/admin/show/lang/en_US')
-        assert b'An essential ' in rv.data, rv.data
-        rv = self.app.get('/admin/show/lang/fr_FR')
-        assert b'An essential ' not in rv.data, rv.data
+        rv = self.app.get("/admin/show/lang/en_US")
+        assert b"An essential " in rv.data, rv.data
+        rv = self.app.get("/admin/show/lang/fr_FR")
+        assert b"An essential " not in rv.data, rv.data
 
     def test_admin_moderators(self):
 
         self.login()
-        rv = self.app.get('/admin/moderators/all')
+        rv = self.app.get("/admin/moderators/all")
         assert self.user_hash.encode() in rv.data, rv.data
 
     def test_admin_search(self):
 
         self.review_submit()
         self.login()
-        rv = self.app.get('/admin/search?value=notgoingtoexist')
-        assert b'There are no results for this query' in rv.data, rv.data
-        rv = self.app.get('/admin/search?value=inkscape+notgoingtoexist')
-        assert b'Somebody Import' in rv.data, rv.data
-
-    def _admin_taboo_add(self, locale='en', value='inkscape', description='ola!', severity=0):
-        data = {'locale': locale, 'value': value, 'description': description, 'severity': severity}
-        return self.app.post('/admin/taboo/add', data=data, follow_redirects=True)
+        rv = self.app.get("/admin/search?value=notgoingtoexist")
+        assert b"There are no results for this query" in rv.data, rv.data
+        rv = self.app.get("/admin/search?value=inkscape+notgoingtoexist")
+        assert b"Somebody Import" in rv.data, rv.data
+
+    def _admin_taboo_add(
+        self, locale="en", value="inkscape", description="ola!", severity=0
+    ):
+        data = {
+            "locale": locale,
+            "value": value,
+            "description": description,
+            "severity": severity,
+        }
+        return self.app.post("/admin/taboo/add", data=data, follow_redirects=True)
 
     def test_admin_components(self):
 
         self.review_submit()
-        self.review_submit(app_id='inkscape-ubuntu-lts.desktop')
+        self.review_submit(app_id="inkscape-ubuntu-lts.desktop")
         self.login()
-        rv = self.app.get('/admin/component/all')
-        assert b'inkscape.desktop' in rv.data, rv.data
-        assert b'inkscape-ubuntu-lts.desktop' in rv.data, rv.data
-
-        rv = self.app.get('/admin/component/join/notgoingtoexist.desktop/inkscape-ubuntu-lts.desktop', 
follow_redirects=True)
-        assert b'No parent component found' in rv.data, rv.data
-        rv = self.app.get('/admin/component/join/inkscape.desktop/notgoingtoexist.desktop', 
follow_redirects=True)
-        assert b'No child component found' in rv.data, rv.data
-        rv = self.app.get('/admin/component/join/inkscape.desktop/inkscape.desktop', follow_redirects=True)
-        assert b'Parent and child components were the same' in rv.data, rv.data
-        rv = self.app.get('/admin/component/join/inkscape.desktop/inkscape-ubuntu-lts.desktop', 
follow_redirects=True)
-        assert b'Joined components' in rv.data, rv.data
+        rv = self.app.get("/admin/component/all")
+        assert b"inkscape.desktop" in rv.data, rv.data
+        assert b"inkscape-ubuntu-lts.desktop" in rv.data, rv.data
+
+        rv = self.app.get(
+            "/admin/component/join/notgoingtoexist.desktop/inkscape-ubuntu-lts.desktop",
+            follow_redirects=True,
+        )
+        assert b"No parent component found" in rv.data, rv.data
+        rv = self.app.get(
+            "/admin/component/join/inkscape.desktop/notgoingtoexist.desktop",
+            follow_redirects=True,
+        )
+        assert b"No child component found" in rv.data, rv.data
+        rv = self.app.get(
+            "/admin/component/join/inkscape.desktop/inkscape.desktop",
+            follow_redirects=True,
+        )
+        assert b"Parent and child components were the same" in rv.data, rv.data
+        rv = self.app.get(
+            "/admin/component/join/inkscape.desktop/inkscape-ubuntu-lts.desktop",
+            follow_redirects=True,
+        )
+        assert b"Joined components" in rv.data, rv.data
 
         # again
-        rv = self.app.get('/admin/component/join/inkscape.desktop/inkscape-ubuntu-lts.desktop', 
follow_redirects=True)
-        assert b'Parent and child already set up' in rv.data, rv.data
+        rv = self.app.get(
+            "/admin/component/join/inkscape.desktop/inkscape-ubuntu-lts.desktop",
+            follow_redirects=True,
+        )
+        assert b"Parent and child already set up" in rv.data, rv.data
 
         # delete inkscape.desktop
         rv = self._api_review_delete()
-        assert b'removed review #1' in rv.data, rv.data
+        assert b"removed review #1" in rv.data, rv.data
 
         # still match for the alternate name
         self.review_fetch()
 
     def test_admin_component_delete(self):
         self.review_submit()
-        self.review_submit(app_id='inkscape-ubuntu-lts.desktop')
+        self.review_submit(app_id="inkscape-ubuntu-lts.desktop")
         self.login()
 
         # delete one, causing the review to get deleted too
-        rv = self.app.get('/admin/component/delete/99999', follow_redirects=True)
-        assert b'Unable to find component' in rv.data, rv.data
-        rv = self.app.get('/admin/component/delete/2', follow_redirects=True)
-        assert b'Deleted component with 1 reviews' in rv.data, rv.data
+        rv = self.app.get("/admin/component/delete/99999", follow_redirects=True)
+        assert b"Unable to find component" in rv.data, rv.data
+        rv = self.app.get("/admin/component/delete/2", follow_redirects=True)
+        assert b"Deleted component with 1 reviews" in rv.data, rv.data
 
         # still match for the alternate name
         self.review_fetch()
 
-        rv = self.app.get('/admin/component/delete/1', follow_redirects=True)
-        assert b'Deleted component with 1 reviews' in rv.data, rv.data
+        rv = self.app.get("/admin/component/delete/1", follow_redirects=True)
+        assert b"Deleted component with 1 reviews" in rv.data, rv.data
 
     def test_admin_taboo(self):
 
         self.login()
 
-        rv = self.app.get('/admin/taboo/all')
-        assert b'There are no taboos stored' in rv.data, rv.data
+        rv = self.app.get("/admin/taboo/all")
+        assert b"There are no taboos stored" in rv.data, rv.data
 
         # add taboos
         rv = self._admin_taboo_add()
-        assert b'Added taboo' in rv.data, rv.data
-        assert b'inkscape' in rv.data, rv.data
+        assert b"Added taboo" in rv.data, rv.data
+        assert b"inkscape" in rv.data, rv.data
         rv = self._admin_taboo_add()
-        assert b'Already added that taboo' in rv.data, rv.data
-        rv = self._admin_taboo_add(locale='fr_FR')
-        assert b'Added taboo' in rv.data, rv.data
+        assert b"Already added that taboo" in rv.data, rv.data
+        rv = self._admin_taboo_add(locale="fr_FR")
+        assert b"Added taboo" in rv.data, rv.data
 
         # submit something, and ensure it's flagged
         self.review_submit()
-        rv = self.app.get('/admin/review/1')
-        assert b'Somebody Important' in rv.data, rv.data
-        assert b'Contains taboo' in rv.data, rv.data
+        rv = self.app.get("/admin/review/1")
+        assert b"Somebody Important" in rv.data, rv.data
+        assert b"Contains taboo" in rv.data, rv.data
 
         # delete
-        rv = self.app.get('/admin/taboo/1/delete', follow_redirects=True)
-        assert b'Deleted taboo' in rv.data, rv.data
-        rv = self.app.get('/admin/taboo/1/delete', follow_redirects=True)
-        assert b'No taboo with ID' in rv.data, rv.data
+        rv = self.app.get("/admin/taboo/1/delete", follow_redirects=True)
+        assert b"Deleted taboo" in rv.data, rv.data
+        rv = self.app.get("/admin/taboo/1/delete", follow_redirects=True)
+        assert b"No taboo with ID" in rv.data, rv.data
 
     def test_api_submit_when_banned(self):
 
@@ -308,35 +354,38 @@ class OdrsTest(unittest.TestCase):
 
         # add user to the ban list
         self.login()
-        rv = self.app.get('/admin/user_ban/{}'.format(self.user_hash), follow_redirects=True)
-        assert b'Banned user' in rv.data, rv.data
-        assert b'deleted 1 reviews' in rv.data, rv.data
+        rv = self.app.get(
+            "/admin/user_ban/{}".format(self.user_hash), follow_redirects=True
+        )
+        assert b"Banned user" in rv.data, rv.data
+        assert b"deleted 1 reviews" in rv.data, rv.data
         self.logout()
 
         # try to submit another review
-        rv = self._review_submit(app_id='gimp.desktop')
-        assert b'account has been disabled due to abuse' in rv.data, rv.data
+        rv = self._review_submit(app_id="gimp.desktop")
+        assert b"account has been disabled due to abuse" in rv.data, rv.data
 
     def test_login_logout(self):
 
         # test logging in and out
-        rv = self._login('admin test com', 'Pa$$w0rd')
-        assert b'/admin/show/reported' in rv.data, rv.data
+        rv = self._login("admin test com", "Pa$$w0rd")
+        assert b"/admin/show/reported" in rv.data, rv.data
         rv = self._logout()
-        rv = self._login('admin test com', 'Pa$$w0rd')
-        assert b'/admin/show/reported' in rv.data, rv.data
+        rv = self._login("admin test com", "Pa$$w0rd")
+        assert b"/admin/show/reported" in rv.data, rv.data
         rv = self._logout()
-        assert b'/admin/show/reported' not in rv.data, rv.data
-        rv = self._login('FAILED test com', 'default')
-        assert b'Incorrect username' in rv.data, rv.data
-        rv = self._login('admin test com', 'defaultx')
-        assert b'Incorrect password' in rv.data, rv.data
+        assert b"/admin/show/reported" not in rv.data, rv.data
+        rv = self._login("FAILED test com", "default")
+        assert b"Incorrect username" in rv.data, rv.data
+        rv = self._login("admin test com", "defaultx")
+        assert b"Incorrect password" in rv.data, rv.data
 
     @staticmethod
-    def run_cron_regenerate_ratings(fn='test.json'):
+    def run_cron_regenerate_ratings(fn="test.json"):
 
         from odrs import app
         from cron import _regenerate_ratings
+
         with app.test_request_context():
             _regenerate_ratings(fn)
 
@@ -345,143 +394,165 @@ class OdrsTest(unittest.TestCase):
 
         from odrs import app
         from cron import _auto_delete
+
         with app.test_request_context():
             _auto_delete(0)
 
     def test_nologin_required(self):
 
         # all these are viewable without being logged in
-        uris = ['/',
-                '/privacy',
-                ]
+        uris = [
+            "/",
+            "/privacy",
+        ]
         for uri in uris:
             rv = self.app.get(uri, follow_redirects=True)
-            assert b'favicon.ico' in rv.data, rv.data
-            assert b'Error!' not in rv.data, rv.data
-
-    def _review_submit(self, app_id=None, locale='en_US', distro='Fedora',
-                       version='2:1.2.3~dsg',
-                       summary=' An essential part of my daily workflow',
-                       user_hash=None, user_skey=None,
-                       user_display='Somebody Important'):
+            assert b"favicon.ico" in rv.data, rv.data
+            assert b"Error!" not in rv.data, rv.data
+
+    def _review_submit(
+        self,
+        app_id=None,
+        locale="en_US",
+        distro="Fedora",
+        version="2:1.2.3~dsg",
+        summary=" An essential part of my daily workflow",
+        user_hash=None,
+        user_skey=None,
+        user_display="Somebody Important",
+    ):
         if not app_id:
-            app_id = 'inkscape.desktop'
+            app_id = "inkscape.desktop"
         if not user_hash:
             user_hash = self.user_hash
         if not user_skey:
             user_skey = _get_user_key(user_hash, app_id)
         # upload a review
-        data = {'app_id': app_id,
-                'locale': locale,
-                'summary': summary,
-                'description': 'Inkscape has been a essential part of my workflow for many years now.',
-                'user_hash': user_hash,
-                'user_skey': user_skey,
-                'user_display': user_display,
-                'distro': distro,
-                'rating': 100,
-                'version': version}
-        return self.app.post('/1.0/reviews/api/submit', data=json.dumps(data), follow_redirects=True)
+        data = {
+            "app_id": app_id,
+            "locale": locale,
+            "summary": summary,
+            "description": "Inkscape has been a essential part of my workflow for many years now.",
+            "user_hash": user_hash,
+            "user_skey": user_skey,
+            "user_display": user_display,
+            "distro": distro,
+            "rating": 100,
+            "version": version,
+        }
+        return self.app.post(
+            "/1.0/reviews/api/submit", data=json.dumps(data), follow_redirects=True
+        )
 
     def review_submit(self, app_id=None, user_hash=None):
         rv = self._review_submit(app_id=app_id, user_hash=user_hash)
         assert b'"success": true' in rv.data, rv.data
 
-    def _review_fetch(self,
-                      app_id='inkscape.desktop',
-                      user_hash=None,
-                      locale='en_US',
-                      distro='Fedora',
-                      compat_ids=None,
-                      version='1.2.3'):
+    def _review_fetch(
+        self,
+        app_id="inkscape.desktop",
+        user_hash=None,
+        locale="en_US",
+        distro="Fedora",
+        compat_ids=None,
+        version="1.2.3",
+    ):
         if not user_hash:
             user_hash = self.user_hash
         # fetch some reviews
-        data = {'app_id': app_id,
-                'user_hash': user_hash,
-                'locale': locale,
-                'distro': distro,
-                'limit': 5,
-                'version': version}
+        data = {
+            "app_id": app_id,
+            "user_hash": user_hash,
+            "locale": locale,
+            "distro": distro,
+            "limit": 5,
+            "version": version,
+        }
         if compat_ids:
-            data['compat_ids'] = compat_ids
-        return self.app.post('/1.0/reviews/api/fetch', data=json.dumps(data), follow_redirects=True)
+            data["compat_ids"] = compat_ids
+        return self.app.post(
+            "/1.0/reviews/api/fetch", data=json.dumps(data), follow_redirects=True
+        )
 
     def review_fetch(self):
-        rv = self._review_fetch(app_id='inkscape.desktop')
-        assert b'An essential part of my daily workflow' in rv.data, rv.data
+        rv = self._review_fetch(app_id="inkscape.desktop")
+        assert b"An essential part of my daily workflow" in rv.data, rv.data
 
     def test_api_moderate_locale(self):
 
-        rv = self.app.get('/1.0/reviews/api/moderate/{}/en_GB'.format(self.user_hash))
-        assert rv.data == b'[]', rv.data
+        rv = self.app.get("/1.0/reviews/api/moderate/{}/en_GB".format(self.user_hash))
+        assert rv.data == b"[]", rv.data
         self.review_submit()
-        rv = self.app.get('/1.0/reviews/api/moderate/{}/en_GB'.format(self.user_hash))
-        assert b'Somebody Important' in rv.data, rv.data
-        rv = self.app.get('/1.0/reviews/api/moderate/{}/fr_FR'.format(self.user_hash))
-        assert rv.data == b'[]', rv.data
+        rv = self.app.get("/1.0/reviews/api/moderate/{}/en_GB".format(self.user_hash))
+        assert b"Somebody Important" in rv.data, rv.data
+        rv = self.app.get("/1.0/reviews/api/moderate/{}/fr_FR".format(self.user_hash))
+        assert rv.data == b"[]", rv.data
 
     def test_api_fetch_no_results(self):
 
         # get the skey back for an app with no reviews
-        rv = self._review_fetch(app_id='not-going-to-exist.desktop')
-        assert b'An essential part of my daily workflow' not in rv.data, rv.data
-        assert b'user_skey' in rv.data, rv.data
+        rv = self._review_fetch(app_id="not-going-to-exist.desktop")
+        assert b"An essential part of my daily workflow" not in rv.data, rv.data
+        assert b"user_skey" in rv.data, rv.data
 
     def test_api_fetch_compat_id(self):
 
         self.review_submit()
 
         # get the reviews back for the app using compat IDs
-        rv = self._review_fetch(app_id='foo.desktop', compat_ids=['inkscape.desktop'])
-        assert b'An essential part of my daily workflow' in rv.data, rv.data
-        assert b'user_skey' in rv.data, rv.data
+        rv = self._review_fetch(app_id="foo.desktop", compat_ids=["inkscape.desktop"])
+        assert b"An essential part of my daily workflow" in rv.data, rv.data
+        assert b"user_skey" in rv.data, rv.data
 
     def review_upvote(self, user_hash=None):
         if not user_hash:
             user_hash = self.user_hash
-        data = {'review_id': 1,
-                'app_id': 'inkscape.desktop',
-                'user_hash': user_hash,
-                'user_skey': _get_user_key(user_hash, 'inkscape.desktop')}
-        return self.app.post('/1.0/reviews/api/upvote', data=json.dumps(data))
+        data = {
+            "review_id": 1,
+            "app_id": "inkscape.desktop",
+            "user_hash": user_hash,
+            "user_skey": _get_user_key(user_hash, "inkscape.desktop"),
+        }
+        return self.app.post("/1.0/reviews/api/upvote", data=json.dumps(data))
 
     def review_report(self, user_hash=None):
         if not user_hash:
             user_hash = self.user_hash
-        data = {'review_id': 1,
-                'app_id': 'inkscape.desktop',
-                'user_hash': user_hash,
-                'user_skey': _get_user_key(user_hash, 'inkscape.desktop')}
-        return self.app.post('/1.0/reviews/api/report', data=json.dumps(data))
+        data = {
+            "review_id": 1,
+            "app_id": "inkscape.desktop",
+            "user_hash": user_hash,
+            "user_skey": _get_user_key(user_hash, "inkscape.desktop"),
+        }
+        return self.app.post("/1.0/reviews/api/report", data=json.dumps(data))
 
     def test_api_upvote(self):
 
         # does not exist
         rv = self.review_upvote()
-        assert b'invalid review ID' in rv.data, rv.data
+        assert b"invalid review ID" in rv.data, rv.data
 
         # first upvote
         self.review_submit()
         rv = self.review_upvote()
         assert b'success": true' in rv.data, rv.data
-        assert b'voted #1 1' in rv.data, rv.data
+        assert b"voted #1 1" in rv.data, rv.data
 
         # duplicate upvote
         rv = self.review_upvote()
         assert b'success": false' in rv.data, rv.data
-        assert b'already voted on this app' in rv.data, rv.data
+        assert b"already voted on this app" in rv.data, rv.data
 
         # check vote_id is set
-        rv = self._review_fetch(app_id='inkscape.desktop')
+        rv = self._review_fetch(app_id="inkscape.desktop")
         assert b'vote_id": 1' in rv.data, rv.data
 
         # delete review, hopefully deleting vote too
         rv = self._api_review_delete()
-        assert b'removed review #1' in rv.data, rv.data
+        assert b"removed review #1" in rv.data, rv.data
         self.run_cron_auto_delete()
-        rv = self._review_fetch(app_id='inkscape.desktop')
-        assert b'vote_id' not in rv.data, rv.data
+        rv = self._review_fetch(app_id="inkscape.desktop")
+        assert b"vote_id" not in rv.data, rv.data
 
     def test_api_report(self):
 
@@ -492,77 +563,85 @@ class OdrsTest(unittest.TestCase):
         # should not appear again
         rv = self.review_report()
         assert b'success": true' in rv.data, rv.data
-        assert b'voted #1 -5' in rv.data, rv.data
-        rv = self.review_report(user_hash='729342d6a7c477bb1ea0186f8c60804a3d783183')
+        assert b"voted #1 -5" in rv.data, rv.data
+        rv = self.review_report(user_hash="729342d6a7c477bb1ea0186f8c60804a3d783183")
         assert b'success": true' in rv.data, rv.data
-        assert b'voted #1 -5' in rv.data, rv.data
-        rv = self._review_fetch(app_id='inkscape.desktop')
-        assert b'An essential part of my daily workflow' not in rv.data, rv.data
+        assert b"voted #1 -5" in rv.data, rv.data
+        rv = self._review_fetch(app_id="inkscape.desktop")
+        assert b"An essential part of my daily workflow" not in rv.data, rv.data
 
         # duplicate upvote
         rv = self.review_upvote()
         assert b'success": false' in rv.data, rv.data
-        assert b'already voted on this app' in rv.data, rv.data
+        assert b"already voted on this app" in rv.data, rv.data
 
     def test_api_app_rating(self):
 
         # nothing
-        rv = self.app.get('/1.0/reviews/api/ratings/not-going-to-exist.desktop')
-        assert rv.data == b'[]', rv.data
+        rv = self.app.get("/1.0/reviews/api/ratings/not-going-to-exist.desktop")
+        assert rv.data == b"[]", rv.data
 
         # something
         self.review_submit()
-        rv = self.app.get('/1.0/reviews/api/ratings/inkscape.desktop')
+        rv = self.app.get("/1.0/reviews/api/ratings/inkscape.desktop")
         assert b'star1": 0' in rv.data, rv.data
         assert b'star5": 1' in rv.data, rv.data
         assert b'total": 1' in rv.data, rv.data
 
         # all
-        self.review_submit(user_hash='0000000000000000000000000000000000000000')
-        rv = self.app.get('/1.0/reviews/api/ratings')
-        assert b'inkscape.desktop' in rv.data, rv.data
+        self.review_submit(user_hash="0000000000000000000000000000000000000000")
+        rv = self.app.get("/1.0/reviews/api/ratings")
+        assert b"inkscape.desktop" in rv.data, rv.data
         assert b'star1": 0' in rv.data, rv.data
         assert b'star5": 2' in rv.data, rv.data
         assert b'total": 2' in rv.data, rv.data
 
     def _api_review_delete(self):
-        data = {'review_id': 1,
-                'app_id': 'inkscape.desktop',
-                'user_hash': self.user_hash,
-                'user_skey': _get_user_key(self.user_hash, 'inkscape.desktop')}
-        return self.app.post('/1.0/reviews/api/remove', data=json.dumps(data))
+        data = {
+            "review_id": 1,
+            "app_id": "inkscape.desktop",
+            "user_hash": self.user_hash,
+            "user_skey": _get_user_key(self.user_hash, "inkscape.desktop"),
+        }
+        return self.app.post("/1.0/reviews/api/remove", data=json.dumps(data))
 
     def test_api_remove(self):
 
         self.review_submit()
 
         # wrong app_id
-        data = {'review_id': 1,
-                'app_id': 'dave.desktop',
-                'user_hash': self.user_hash,
-                'user_skey': _get_user_key(self.user_hash, 'dave.desktop')}
-        rv = self.app.post('/1.0/reviews/api/remove', data=json.dumps(data))
-        assert b'the app_id is invalid' in rv.data, rv.data
+        data = {
+            "review_id": 1,
+            "app_id": "dave.desktop",
+            "user_hash": self.user_hash,
+            "user_skey": _get_user_key(self.user_hash, "dave.desktop"),
+        }
+        rv = self.app.post("/1.0/reviews/api/remove", data=json.dumps(data))
+        assert b"the app_id is invalid" in rv.data, rv.data
 
         # wrong user_hash
-        data = {'review_id': 1,
-                'app_id': 'inkscape.desktop',
-                'user_hash': _get_user_key(self.user_hash, 'inkscape.desktop'),
-                'user_skey': _get_user_key(self.user_hash, 'inkscape.desktop')}
-        rv = self.app.post('/1.0/reviews/api/remove', data=json.dumps(data))
-        assert b'no review' in rv.data, rv.data
+        data = {
+            "review_id": 1,
+            "app_id": "inkscape.desktop",
+            "user_hash": _get_user_key(self.user_hash, "inkscape.desktop"),
+            "user_skey": _get_user_key(self.user_hash, "inkscape.desktop"),
+        }
+        rv = self.app.post("/1.0/reviews/api/remove", data=json.dumps(data))
+        assert b"no review" in rv.data, rv.data
 
         # wrong user_skey
-        data = {'review_id': 1,
-                'app_id': 'inkscape.desktop',
-                'user_hash': self.user_hash,
-                'user_skey': self.user_hash}
-        rv = self.app.post('/1.0/reviews/api/remove', data=json.dumps(data))
-        assert b'invalid user_skey' in rv.data, rv.data
+        data = {
+            "review_id": 1,
+            "app_id": "inkscape.desktop",
+            "user_hash": self.user_hash,
+            "user_skey": self.user_hash,
+        }
+        rv = self.app.post("/1.0/reviews/api/remove", data=json.dumps(data))
+        assert b"invalid user_skey" in rv.data, rv.data
 
         # delete a review
         rv = self._api_review_delete()
-        assert b'removed review #1' in rv.data, rv.data
+        assert b"removed review #1" in rv.data, rv.data
 
     def test_api_submit(self):
 
@@ -571,59 +650,64 @@ class OdrsTest(unittest.TestCase):
         assert b'"success": true' in rv.data, rv.data
 
         # upload a 2nd report
-        rv = self._review_submit(app_id='gimp.desktop')
+        rv = self._review_submit(app_id="gimp.desktop")
         assert b'"success": true' in rv.data, rv.data
 
         # upload a duplicate report
         rv = self._review_submit()
         assert b'success": false' in rv.data, rv.data
-        assert b'already reviewed this app' in rv.data, rv.data
+        assert b"already reviewed this app" in rv.data, rv.data
 
         # upload an invalid report
-        rv = self._review_submit(summary='<html>foo</html>')
+        rv = self._review_submit(summary="<html>foo</html>")
         assert b'success": false' in rv.data, rv.data
-        assert b'is not a valid string' in rv.data, rv.data
-        rv = self._review_submit(summary='')
+        assert b"is not a valid string" in rv.data, rv.data
+        rv = self._review_submit(summary="")
         assert b'success": false' in rv.data, rv.data
-        assert b'missing data' in rv.data, rv.data
+        assert b"missing data" in rv.data, rv.data
 
         # get the review back
-        rv = self.app.get('/1.0/reviews/api/app/inkscape.desktop')
-        assert b'Somebody Important' in rv.data, rv.data
-        assert b'An essential part of my daily workflow' in rv.data, rv.data
-        assert b'user_skey' not in rv.data, rv.data
+        rv = self.app.get("/1.0/reviews/api/app/inkscape.desktop")
+        assert b"Somebody Important" in rv.data, rv.data
+        assert b"An essential part of my daily workflow" in rv.data, rv.data
+        assert b"user_skey" not in rv.data, rv.data
 
         # get the review back with skey
-        rv = self.app.get('/1.0/reviews/api/app/inkscape.desktop/{}'.format(self.user_hash))
-        assert b'An essential part of my daily workflow' in rv.data, rv.data
-        assert b'user_skey' in rv.data, rv.data
+        rv = self.app.get(
+            "/1.0/reviews/api/app/inkscape.desktop/{}".format(self.user_hash)
+        )
+        assert b"An essential part of my daily workflow" in rv.data, rv.data
+        assert b"user_skey" in rv.data, rv.data
 
         # get the reviews back for the app
-        rv = self._review_fetch(distro='Ubuntu', version='1.2.4')
-        assert b'An essential part of my daily workflow' in rv.data, rv.data
-        assert b'user_skey' in rv.data, rv.data
+        rv = self._review_fetch(distro="Ubuntu", version="1.2.4")
+        assert b"An essential part of my daily workflow" in rv.data, rv.data
+        assert b"user_skey" in rv.data, rv.data
 
     def test_fail_when_login_required(self):
 
         # all these are an error when not logged in
-        uris = ['/admin/graph_month',
-                '/admin/graph_year',
-                '/admin/stats',
-                '/admin/user_ban/1',
-                '/admin/show/reported',
-                '/admin/stats',
-                '/admin/moderators/all']
+        uris = [
+            "/admin/graph_month",
+            "/admin/graph_year",
+            "/admin/stats",
+            "/admin/user_ban/1",
+            "/admin/show/reported",
+            "/admin/stats",
+            "/admin/moderators/all",
+        ]
         for uri in uris:
             rv = self.app.get(uri, follow_redirects=True)
-            assert b'favicon.ico' in rv.data, rv.data
-            assert b'Please log in to access this page' in rv.data, (uri, rv.data)
+            assert b"favicon.ico" in rv.data, rv.data
+            assert b"Please log in to access this page" in rv.data, (uri, rv.data)
 
         # POST only
-        uris = ['/admin/modify/1']
+        uris = ["/admin/modify/1"]
         for uri in uris:
             rv = self.app.post(uri, follow_redirects=True)
-            assert b'favicon.ico' in rv.data, rv.data
-            assert b'Please log in to access this page' in rv.data, rv.data
+            assert b"favicon.ico" in rv.data, rv.data
+            assert b"Please log in to access this page" in rv.data, rv.data
+
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     unittest.main()
diff --git a/app_data/odrs/tests/util_test.py b/app_data/odrs/tests/util_test.py
index 8187f0c..16bdf48 100644
--- a/app_data/odrs/tests/util_test.py
+++ b/app_data/odrs/tests/util_test.py
@@ -13,43 +13,56 @@ import sys
 import unittest
 
 # allows us to run this from the project root
-sys.path.append(os.path.realpath('.'))
+sys.path.append(os.path.realpath("."))
 
 from odrs.util import json_success, json_error, _locale_is_compatible
 from odrs.util import _get_user_key, _password_hash
 from odrs.util import _sanitised_version, _sanitised_summary, _sanitised_description
 
-class UtilTest(unittest.TestCase):
 
+class UtilTest(unittest.TestCase):
     def test_sanitise(self):
 
-        self.assertEqual(_sanitised_version('16.12.3'), '16.12.3')
-        self.assertEqual(_sanitised_version('0:1.2.3+rh'), '1.2.3')
-        self.assertEqual(_sanitised_version('16.11.0~ds0'), '16.11.0')
-        self.assertEqual(_sanitised_summary('   not sure why people include.   '), 'not sure why people 
include')
-        self.assertEqual(_sanitised_description('   this is awesome :) !!   '), 'this is awesome !!')
+        self.assertEqual(_sanitised_version("16.12.3"), "16.12.3")
+        self.assertEqual(_sanitised_version("0:1.2.3+rh"), "1.2.3")
+        self.assertEqual(_sanitised_version("16.11.0~ds0"), "16.11.0")
+        self.assertEqual(
+            _sanitised_summary("   not sure why people include.   "),
+            "not sure why people include",
+        )
+        self.assertEqual(
+            _sanitised_description("   this is awesome :) !!   "), "this is awesome !!"
+        )
 
     def test_response(self):
 
-        self.assertEqual(str(json_success('ok')), '<Response 40 bytes [200 OK]>')
-        self.assertEqual(str(json_error('nok')), '<Response 42 bytes [400 BAD REQUEST]>')
+        self.assertEqual(str(json_success("ok")), "<Response 40 bytes [200 OK]>")
+        self.assertEqual(
+            str(json_error("nok")), "<Response 42 bytes [400 BAD REQUEST]>"
+        )
 
     def test_locale(self):
 
-        self.assertTrue(_locale_is_compatible('en_GB', 'en_GB'))
-        self.assertTrue(_locale_is_compatible('en_GB', 'en_AU'))
-        self.assertTrue(_locale_is_compatible('en_GB', 'C'))
-        self.assertTrue(_locale_is_compatible('C', 'en_GB'))
-        self.assertFalse(_locale_is_compatible('fr_FR', 'en_GB'))
+        self.assertTrue(_locale_is_compatible("en_GB", "en_GB"))
+        self.assertTrue(_locale_is_compatible("en_GB", "en_AU"))
+        self.assertTrue(_locale_is_compatible("en_GB", "C"))
+        self.assertTrue(_locale_is_compatible("C", "en_GB"))
+        self.assertFalse(_locale_is_compatible("fr_FR", "en_GB"))
 
     def test_user_key(self):
 
-        os.environ['ODRS_REVIEWS_SECRET'] = '1'
-        self.assertEqual(_get_user_key('foo', 'gimp.desktop'), '8d68a9e8054a18cb11e62242f9036aca786551d8')
+        os.environ["ODRS_REVIEWS_SECRET"] = "1"
+        self.assertEqual(
+            _get_user_key("foo", "gimp.desktop"),
+            "8d68a9e8054a18cb11e62242f9036aca786551d8",
+        )
 
     def test_legacy_hash(self):
 
-        self.assertEqual(_password_hash('foo'), '9cab340b3184a1f792d6629806703aed450ecd48')
+        self.assertEqual(
+            _password_hash("foo"), "9cab340b3184a1f792d6629806703aed450ecd48"
+        )
+
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     unittest.main()
diff --git a/app_data/odrs/util.py b/app_data/odrs/util.py
index bc13dad..51e76e9 100644
--- a/app_data/odrs/util.py
+++ b/app_data/odrs/util.py
@@ -14,63 +14,70 @@ from sqlalchemy import or_
 
 from flask import Response
 
+
 def json_success(msg=None, errcode=200):
-    """ Success handler: JSON output """
+    """Success handler: JSON output"""
     item = {}
-    item['success'] = True
+    item["success"] = True
     if msg:
-        item['msg'] = msg
-    dat = json.dumps(item, sort_keys=True, indent=4, separators=(',', ': '))
-    return Response(response=dat,
-                    status=errcode, \
-                    mimetype='application/json')
+        item["msg"] = msg
+    dat = json.dumps(item, sort_keys=True, indent=4, separators=(",", ": "))
+    return Response(response=dat, status=errcode, mimetype="application/json")
+
 
 def json_error(msg=None, errcode=400):
-    """ Error handler: JSON output """
+    """Error handler: JSON output"""
     item = {}
-    item['success'] = False
+    item["success"] = False
     if msg:
-        item['msg'] = msg
-    dat = json.dumps(item, sort_keys=True, indent=4, separators=(',', ': '))
-    return Response(response=dat,
-                    status=errcode, \
-                    mimetype='application/json')
+        item["msg"] = msg
+    dat = json.dumps(item, sort_keys=True, indent=4, separators=(",", ": "))
+    return Response(response=dat, status=errcode, mimetype="application/json")
+
 
 def _get_datestr_from_dt(when):
-    return int('%04i%02i%02i' % (when.year, when.month, when.day))
+    return int("%04i%02i%02i" % (when.year, when.month, when.day))
+
 
 def _get_user_key(user_hash, app_id):
     from odrs import app
-    key = 'invalid'
+
+    key = "invalid"
     try:
-        key = hashlib.sha1(app.secret_key.encode('utf-8') +
-                           user_hash.encode('utf-8') +
-                           app_id.encode('utf-8')).hexdigest()
+        key = hashlib.sha1(
+            app.secret_key.encode("utf-8")
+            + user_hash.encode("utf-8")
+            + app_id.encode("utf-8")
+        ).hexdigest()
     except UnicodeEncodeError as e:
-        print('invalid input: %s,%s: %s' % (user_hash, app_id, str(e)))
+        print("invalid input: %s,%s: %s" % (user_hash, app_id, str(e)))
     return key
 
-def _eventlog_add(user_addr=None,
-                  user_id=None,
-                  app_id=None,
-                  message=None,
-                  important=False):
-    """ Adds a warning to the event log """
+
+def _eventlog_add(
+    user_addr=None, user_id=None, app_id=None, message=None, important=False
+):
+    """Adds a warning to the event log"""
     from .models import Event
     from odrs import db
+
     db.session.add(Event(user_addr, user_id, app_id, message, important))
     db.session.commit()
 
+
 def _get_rating_for_component(component, min_total=1):
-    """ Gets the ratings information for the application """
+    """Gets the ratings information for the application"""
     from odrs import db
     from odrs.models import Review, Component
 
     # get all ratings for app
     array = [0] * 6
-    for rating in db.session.query(Review.rating).\
-                        join(Component).\
-                        filter(Component.app_id.in_(component.app_ids)).all():
+    for rating in (
+        db.session.query(Review.rating)
+        .join(Component)
+        .filter(Component.app_id.in_(component.app_ids))
+        .all()
+    ):
         idx = int(rating[0] / 20)
         if idx > 5:
             continue
@@ -81,33 +88,44 @@ def _get_rating_for_component(component, min_total=1):
         return []
 
     # return as dict
-    item = {'total': sum(array)}
+    item = {"total": sum(array)}
     for idx in range(6):
-        item['star{}'.format(idx)] = array[idx]
+        item["star{}".format(idx)] = array[idx]
     return item
 
+
 def _password_hash(value):
-    """ Generate a legacy salted hash of the password string """
-    salt = 'odrs%%%'
-    return hashlib.sha1(salt.encode('utf-8') + value.encode('utf-8')).hexdigest()
+    """Generate a legacy salted hash of the password string"""
+    salt = "odrs%%%"
+    return hashlib.sha1(salt.encode("utf-8") + value.encode("utf-8")).hexdigest()
+
 
 def _addr_hash(value):
-    """ Generate a salted hash of the IP address """
+    """Generate a salted hash of the IP address"""
     from odrs import app
-    return hashlib.sha1((app.secret_key + value).encode('utf-8')).hexdigest()
+
+    return hashlib.sha1((app.secret_key + value).encode("utf-8")).hexdigest()
+
 
 def _get_taboos_for_locale(locale):
     from .models import Taboo
     from odrs import db
-    if locale.find('_') != -1:
-        lang, _ = locale.split('_', maxsplit=1)
-        return db.session.query(Taboo).\
-                        filter(or_(Taboo.locale == locale,
-                                   Taboo.locale == lang,
-                                   Taboo.locale == 'en')).all()
-    return db.session.query(Taboo).\
-                    filter(or_(Taboo.locale == locale,
-                               Taboo.locale == 'en')).all()
+
+    if locale.find("_") != -1:
+        lang, _ = locale.split("_", maxsplit=1)
+        return (
+            db.session.query(Taboo)
+            .filter(
+                or_(Taboo.locale == locale, Taboo.locale == lang, Taboo.locale == "en")
+            )
+            .all()
+        )
+    return (
+        db.session.query(Taboo)
+        .filter(or_(Taboo.locale == locale, Taboo.locale == "en"))
+        .all()
+    )
+
 
 def _sanitised_input(val):
 
@@ -115,53 +133,57 @@ def _sanitised_input(val):
     val = val.strip()
 
     # fix up style issues
-    val = val.replace('!!!', '!')
-    val = val.replace(':)', '')
-    val = val.replace('  ', ' ')
+    val = val.replace("!!!", "!")
+    val = val.replace(":)", "")
+    val = val.replace("  ", " ")
 
     return val
 
+
 def _sanitised_summary(val):
     val = _sanitised_input(val)
-    if val.endswith('.'):
-        val = val[:len(val)-1]
+    if val.endswith("."):
+        val = val[: len(val) - 1]
     return val
 
+
 def _sanitised_description(val):
     return _sanitised_input(val)
 
+
 def _sanitised_version(val):
 
     # remove epoch
-    idx = val.find(':')
+    idx = val.find(":")
     if idx != -1:
-        val = val[idx+1:]
+        val = val[idx + 1 :]
 
     # remove distro addition
-    idx = val.find('+')
+    idx = val.find("+")
     if idx != -1:
         val = val[:idx]
-    idx = val.find('~')
+    idx = val.find("~")
     if idx != -1:
         val = val[:idx]
 
     return val
 
+
 def _locale_is_compatible(l1, l2):
-    """ Returns True if the locale is compatible """
+    """Returns True if the locale is compatible"""
 
     # trivial case
     if l1 == l2:
         return True
 
     # language code matches
-    lang1 = l1.split('_')[0]
-    lang2 = l2.split('_')[0]
+    lang1 = l1.split("_")[0]
+    lang2 = l2.split("_")[0]
     if lang1 == lang2:
         return True
 
     # LANG=C
-    en_langs = ['C', 'en']
+    en_langs = ["C", "en"]
     if lang1 in en_langs and lang2 in en_langs:
         return True
 
diff --git a/app_data/odrs/views.py b/app_data/odrs/views.py
index 233434b..c42ff6e 100644
--- a/app_data/odrs/views.py
+++ b/app_data/odrs/views.py
@@ -9,94 +9,120 @@
 
 import os
 
-from flask import request, url_for, redirect, flash, render_template, send_from_directory, g
+from flask import (
+    request,
+    url_for,
+    redirect,
+    flash,
+    render_template,
+    send_from_directory,
+    g,
+)
 from flask_login import login_user, logout_user
 
 from odrs import app, db
 
 from .models import Moderator
 
+
 @app.context_processor
 def _utility_processor():
     def format_rating(rating):
         nr_stars = int(rating / 20)
-        tmp = ''
+        tmp = ""
         for _ in range(0, nr_stars):
-            tmp += '★'
+            tmp += "★"
         for _ in range(0, 5 - nr_stars):
-            tmp += '☆'
+            tmp += "☆"
         return tmp
 
     def format_truncate(tmp, length):
         if len(tmp) <= length:
             return tmp
-        return tmp[:length] + '…'
+        return tmp[:length] + "…"
 
     def url_for_other_page(page):
         args = request.view_args.copy()
-        args['page'] = page
+        args["page"] = page
         return url_for(request.endpoint, **args)
 
-    return dict(format_rating=format_rating,
-                format_truncate=format_truncate,
-                url_for_other_page=url_for_other_page)
+    return dict(
+        format_rating=format_rating,
+        format_truncate=format_truncate,
+        url_for_other_page=url_for_other_page,
+    )
+
 
-@app.route('/login', methods=['GET', 'POST'])
+@app.route("/login", methods=["GET", "POST"])
 def odrs_login():
-    if request.method != 'POST':
-        return render_template('login.html')
-    user = db.session.query(Moderator).filter(Moderator.username == request.form['username']).first()
+    if request.method != "POST":
+        return render_template("login.html")
+    user = (
+        db.session.query(Moderator)
+        .filter(Moderator.username == request.form["username"])
+        .first()
+    )
     if not user:
-        flash('Incorrect username')
-        return redirect(url_for('.odrs_login'))
-    if not user.verify_password(request.form['password']):
-        flash('Incorrect password')
-        return redirect(url_for('.odrs_login'))
+        flash("Incorrect username")
+        return redirect(url_for(".odrs_login"))
+    if not user.verify_password(request.form["password"]):
+        flash("Incorrect password")
+        return redirect(url_for(".odrs_login"))
     login_user(user, remember=False)
     g.user = user
-    flash('Logged in')
-    return redirect(url_for('.odrs_index'))
+    flash("Logged in")
+    return redirect(url_for(".odrs_index"))
+
 
-@app.route('/logout')
+@app.route("/logout")
 def odrs_logout():
     logout_user()
-    flash('Logged out.')
-    return redirect(url_for('.odrs_index'))
+    flash("Logged out.")
+    return redirect(url_for(".odrs_index"))
+
 
 @app.errorhandler(400)
 def _error_internal(msg=None, errcode=400):
-    """ Error handler: Internal """
-    flash('Internal error: %s' % msg)
-    return render_template('error.html'), errcode
+    """Error handler: Internal"""
+    flash("Internal error: %s" % msg)
+    return render_template("error.html"), errcode
+
 
 @app.errorhandler(401)
 def _error_permission_denied(msg=None):
-    """ Error handler: Permission Denied """
-    flash('Permission denied: %s' % msg)
-    return render_template('error.html'), 401
+    """Error handler: Permission Denied"""
+    flash("Permission denied: %s" % msg)
+    return render_template("error.html"), 401
+
 
 @app.errorhandler(404)
 def odrs_error_page_not_found(msg=None):
-    """ Error handler: File not found """
+    """Error handler: File not found"""
     flash(msg)
-    return render_template('error.html'), 404
+    return render_template("error.html"), 404
+
 
-@app.route('/')
+@app.route("/")
 def odrs_index():
-    """ start page """
-    return render_template('index.html')
+    """start page"""
+    return render_template("index.html")
+
 
-@app.route('/privacy')
+@app.route("/privacy")
 def odrs_privacy():
-    """ odrs_privacy page """
-    return render_template('privacy.html')
+    """odrs_privacy page"""
+    return render_template("privacy.html")
 
-@app.route('/oars')
+
+@app.route("/oars")
 def oars_index():
-    """ OARS page """
-    return render_template('oars.html')
+    """OARS page"""
+    return render_template("oars.html")
+
 
-@app.route('/<path:resource>')
+@app.route("/<path:resource>")
 def odrs_static_resource(resource):
-    """ Return a static image or resource """
-    return send_from_directory("%s/odrs/static/" % os.environ['HOME'], os.path.basename(resource))
+    """Return a static image or resource"""
+    return send_from_directory(
+        "%s/odrs/static/" % os.environ["HOME"], os.path.basename(resource)
+    )
diff --git a/app_data/odrs/views_admin.py b/app_data/odrs/views_admin.py
index e34d29e..a43075c 100644
--- a/app_data/odrs/views_admin.py
+++ b/app_data/odrs/views_admin.py
@@ -21,8 +21,9 @@ from .models import Review, User, Moderator, Vote, Taboo, Component
 from .models import _vote_exists
 from .util import _get_datestr_from_dt, _get_taboos_for_locale
 
+
 def _get_chart_labels_months():
-    """ Gets the chart labels """
+    """Gets the chart labels"""
     now = datetime.date.today()
     labels = []
     offset = 0
@@ -32,52 +33,59 @@ def _get_chart_labels_months():
         labels.append(calendar.month_name[now.month - i - offset])
     return labels
 
+
 def _get_chart_labels_days():
-    """ Gets the chart labels """
+    """Gets the chart labels"""
     now = datetime.date.today()
     labels = []
     for i in range(0, 30):
         then = now - datetime.timedelta(i)
-        labels.append('%02i-%02i-%02i' % (then.year, then.month, then.day))
+        labels.append("%02i-%02i-%02i" % (then.year, then.month, then.day))
     return labels
 
+
 def _get_langs_for_user(user):
     if not user:
         return None
-    if not getattr(user, 'locales', None):
+    if not getattr(user, "locales", None):
         return None
     if not user.locales:
         return None
-    if user.locales == '*':
+    if user.locales == "*":
         return None
-    return user.locales.split(',')
+    return user.locales.split(",")
+
 
 def _get_hash_for_user(user):
     if not user:
         return None
-    if not getattr(user, 'user_hash', None):
+    if not getattr(user, "user_hash", None):
         return None
     return user.user_hash
 
+
 def _password_check(value):
-    """ Check the password for suitability """
+    """Check the password for suitability"""
     success = True
     if len(value) < 8:
         success = False
-        flash('The password is too short, the minimum is 8 characters', 'warning')
+        flash("The password is too short, the minimum is 8 characters", "warning")
     if value.isalnum():
         success = False
-        flash('The password requires at least one non-alphanumeric character', 'warning')
+        flash(
+            "The password requires at least one non-alphanumeric character", "warning"
+        )
     return success
 
+
 def _email_check(value):
-    """ Do a quick and dirty check on the email address """
-    if len(value) < 5 or value.find('@') == -1 or value.find('.') == -1:
+    """Do a quick and dirty check on the email address"""
+    if len(value) < 5 or value.find("@") == -1 or value.find(".") == -1:
         return False
     return True
 
-class Pagination():
 
+class Pagination:
     def __init__(self, page, per_page, total_count):
         self.page = page
         self.per_page = per_page
@@ -95,30 +103,40 @@ class Pagination():
     def has_next(self):
         return self.page < self.pages
 
-    def iter_pages(self, left_edge=2, left_current=2,
-                   right_current=5, right_edge=2):
+    def iter_pages(self, left_edge=2, left_current=2, right_current=5, right_edge=2):
         last = 0
         for num in range(1, self.pages + 1):
-            if num <= left_edge or \
-               (num > self.page - left_current - 1 and \
-                num < self.page + right_current) or \
-               num > self.pages - right_edge:
+            if (
+                num <= left_edge
+                or (
+                    num > self.page - left_current - 1
+                    and num < self.page + right_current
+                )
+                or num > self.pages - right_edge
+            ):
                 if last + 1 != num:
                     yield None
                 yield num
                 last = num
 
+
 def _get_analytics_by_interval(size, interval):
-    """ Gets analytics data """
+    """Gets analytics data"""
     array = []
     now = datetime.date.today()
 
     # yes, there's probably a way to do this in one query
     for i in range(size):
-        start = _get_datestr_from_dt(now - datetime.timedelta((i * interval) + interval - 1))
+        start = _get_datestr_from_dt(
+            now - datetime.timedelta((i * interval) + interval - 1)
+        )
         end = _get_datestr_from_dt(now - datetime.timedelta((i * interval) - 1))
-        stmt = text('SELECT fetch_cnt FROM analytics WHERE datestr BETWEEN :start AND :end')
-        res = db.session.execute(stmt.bindparams(start=start, end=end)) # pylint: disable=no-member
+        stmt = text(
+            "SELECT fetch_cnt FROM analytics WHERE datestr BETWEEN :start AND :end"
+        )
+        res = db.session.execute(  # pylint: disable=no-member
+            stmt.bindparams(start=start, end=end)
+        )
 
         # add all these up
         tmp = 0
@@ -127,8 +145,9 @@ def _get_analytics_by_interval(size, interval):
         array.append(tmp)
     return array
 
+
 def _get_stats_by_interval(size, interval, msg):
-    """ Gets stats data """
+    """Gets stats data"""
     cnt = []
     now = datetime.date.today()
 
@@ -136,39 +155,50 @@ def _get_stats_by_interval(size, interval, msg):
     for i in range(size):
         start = now - datetime.timedelta((i * interval) + interval - 1)
         end = now - datetime.timedelta((i * interval) - 1)
-        stmt = text('SELECT COUNT(*) FROM eventlog '
-                    'WHERE message = :msg AND date_created BETWEEN :start AND :end')
-        res = db.session.execute(stmt.bindparams(start=start, end=end, msg=msg)) # pylint: disable=no-member
+        stmt = text(
+            "SELECT COUNT(*) FROM eventlog "
+            "WHERE message = :msg AND date_created BETWEEN :start AND :end"
+        )
+        res = db.session.execute(  # pylint: disable=no-member
+            stmt.bindparams(start=start, end=end, msg=msg)
+        )
         cnt.append(res.fetchone()[0])
     return cnt
 
-@app.route('/admin/graph_month')
+
+@app.route("/admin/graph_month")
 @login_required
 def admin_graph_month():
     """
     Show nice graph graphs.
     """
     data_fetch = _get_analytics_by_interval(30, 1)
-    data_review = _get_stats_by_interval(30, 1, 'reviewed')
-    return render_template('graph-month.html',
-                           labels=_get_chart_labels_days()[::-1],
-                           data_requests=data_fetch[::-1],
-                           data_submitted=data_review[::-1])
+    data_review = _get_stats_by_interval(30, 1, "reviewed")
+    return render_template(
+        "graph-month.html",
+        labels=_get_chart_labels_days()[::-1],
+        data_requests=data_fetch[::-1],
+        data_submitted=data_review[::-1],
+    )
+
 
-@app.route('/admin/graph_year')
+@app.route("/admin/graph_year")
 @login_required
 def admin_graph_year():
     """
     Show nice graph graphs.
     """
     data_fetch = _get_analytics_by_interval(12, 30)
-    data_review = _get_stats_by_interval(12, 30, 'reviewed')
-    return render_template('graph-year.html',
-                           labels=_get_chart_labels_months()[::-1],
-                           data_requests=data_fetch[::-1],
-                           data_submitted=data_review[::-1])
+    data_review = _get_stats_by_interval(12, 30, "reviewed")
+    return render_template(
+        "graph-year.html",
+        labels=_get_chart_labels_months()[::-1],
+        data_requests=data_fetch[::-1],
+        data_submitted=data_review[::-1],
+    )
 
-@app.route('/admin/stats')
+
+@app.route("/admin/stats")
 @login_required
 def admin_show_stats():
     """
@@ -176,95 +206,133 @@ def admin_show_stats():
     """
     # security check
     if not current_user.is_admin:
-        flash('Unable to show stats as non-admin', 'error')
-        return redirect(url_for('.odrs_index'))
+        flash("Unable to show stats as non-admin", "error")
+        return redirect(url_for(".odrs_index"))
 
     stats = {}
 
     # get the total number of reviews
-    rs = db.session.execute("SELECT COUNT(*) FROM reviews;") # pylint: disable=no-member
-    stats['Active reviews'] = rs.fetchone()[0]
+    rs = db.session.execute(  # pylint: disable=no-member
+        "SELECT COUNT(*) FROM reviews;"
+    )
+    stats["Active reviews"] = rs.fetchone()[0]
 
     # unique reviewers
-    rs = db.session.execute("SELECT COUNT(DISTINCT(user_id)) FROM reviews;") # pylint: disable=no-member
-    stats['Unique reviewers'] = rs.fetchone()[0]
+    rs = db.session.execute(  # pylint: disable=no-member
+        "SELECT COUNT(DISTINCT(user_id)) FROM reviews;"
+    )
+    stats["Unique reviewers"] = rs.fetchone()[0]
 
     # total votes
-    rs = db.session.execute("SELECT COUNT(*) FROM votes WHERE val = 1;") # pylint: disable=no-member
-    stats['User upvotes'] = rs.fetchone()[0]
-    rs = db.session.execute("SELECT COUNT(*) FROM votes WHERE val = -1;") # pylint: disable=no-member
-    stats['User downvotes'] = rs.fetchone()[0]
+    rs = db.session.execute(  # pylint: disable=no-member
+        "SELECT COUNT(*) FROM votes WHERE val = 1;"
+    )
+    stats["User upvotes"] = rs.fetchone()[0]
+    rs = db.session.execute(  # pylint: disable=no-member
+        "SELECT COUNT(*) FROM votes WHERE val = -1;"
+    )
+    stats["User downvotes"] = rs.fetchone()[0]
 
     # unique voters
-    rs = db.session.execute("SELECT COUNT(DISTINCT(user_id)) FROM votes;") # pylint: disable=no-member
-    stats['Unique voters'] = rs.fetchone()[0]
+    rs = db.session.execute(  # pylint: disable=no-member
+        "SELECT COUNT(DISTINCT(user_id)) FROM votes;"
+    )
+    stats["Unique voters"] = rs.fetchone()[0]
 
     # unique languages
-    rs = db.session.execute("SELECT COUNT(DISTINCT(locale)) FROM reviews;") # pylint: disable=no-member
-    stats['Unique languages'] = rs.fetchone()[0]
+    rs = db.session.execute(  # pylint: disable=no-member
+        "SELECT COUNT(DISTINCT(locale)) FROM reviews;"
+    )
+    stats["Unique languages"] = rs.fetchone()[0]
 
     # unique distros
-    rs = db.session.execute("SELECT COUNT(DISTINCT(distro)) FROM reviews;") # pylint: disable=no-member
-    stats['Unique distros'] = rs.fetchone()[0]
+    rs = db.session.execute(  # pylint: disable=no-member
+        "SELECT COUNT(DISTINCT(distro)) FROM reviews;"
+    )
+    stats["Unique distros"] = rs.fetchone()[0]
 
     # unique apps
-    rs = db.session.execute("SELECT COUNT(*) FROM components;") # pylint: disable=no-member
-    stats['Unique apps reviewed'] = rs.fetchone()[0]
+    rs = db.session.execute(  # pylint: disable=no-member
+        "SELECT COUNT(*) FROM components;"
+    )
+    stats["Unique apps reviewed"] = rs.fetchone()[0]
 
     # unique distros
-    rs = db.session.execute("SELECT COUNT(*) FROM reviews WHERE reported > 0;") # pylint: disable=no-member
-    stats['Reported reviews'] = rs.fetchone()[0]
+    rs = db.session.execute(  # pylint: disable=no-member
+        "SELECT COUNT(*) FROM reviews WHERE reported > 0;"
+    )
+    stats["Reported reviews"] = rs.fetchone()[0]
 
     # star reviews
     for star in range(1, 6):
-        rs = db.session.execute("SELECT COUNT(*) FROM reviews WHERE rating = {};".format(star * 20)) # 
pylint: disable=no-member
-        stats['%i star reviews' % star] = rs.fetchone()[0]
+        rs = db.session.execute(  # pylint: disable=no-member
+            "SELECT COUNT(*) FROM reviews WHERE rating = {};".format(star * 20)
+        )
+        stats["%i star reviews" % star] = rs.fetchone()[0]
 
     # popularity view
-    viewed = db.session.query(Component.app_id, Component.fetch_cnt).\
-                                    filter(Component.app_id != None).\
-                                    order_by(Component.fetch_cnt.desc()).\
-                                    limit(50).all()
+    viewed = (
+        db.session.query(Component.app_id, Component.fetch_cnt)
+        .filter(Component.app_id != None)
+        .order_by(Component.fetch_cnt.desc())
+        .limit(50)
+        .all()
+    )
 
     # popularity reviews
-    submitted = db.session.query(Component.app_id, Component.review_cnt).\
-                                    filter(Component.app_id != None).\
-                                    order_by(Component.review_cnt.desc()).\
-                                    limit(50).all()
+    submitted = (
+        db.session.query(Component.app_id, Component.review_cnt)
+        .filter(Component.app_id != None)
+        .order_by(Component.review_cnt.desc())
+        .limit(50)
+        .all()
+    )
 
     # users
-    users_awesome = db.session.query(User).\
-                        filter(User.karma != 0).\
-                        order_by(User.karma.desc()).\
-                        limit(10).all()
-    users_haters = db.session.query(User).\
-                        filter(User.karma != 0).\
-                        order_by(User.karma.asc()).\
-                        limit(10).all()
+    users_awesome = (
+        db.session.query(User)
+        .filter(User.karma != 0)
+        .order_by(User.karma.desc())
+        .limit(10)
+        .all()
+    )
+    users_haters = (
+        db.session.query(User)
+        .filter(User.karma != 0)
+        .order_by(User.karma.asc())
+        .limit(10)
+        .all()
+    )
 
     # distros
-    rs = db.session.execute("SELECT DISTINCT(distro), COUNT(distro) AS total " # pylint: disable=no-member
-                            "FROM reviews GROUP BY distro ORDER BY total DESC "
-                            "LIMIT 8;")
+    rs = db.session.execute(  # pylint: disable=no-member
+        "SELECT DISTINCT(distro), COUNT(distro) AS total "
+        "FROM reviews GROUP BY distro ORDER BY total DESC "
+        "LIMIT 8;"
+    )
     labels = []
     data = []
     for s in rs:
         name = s[0]
-        for suffix in [' Linux', ' GNU/Linux', ' OS', ' Linux']:
+        for suffix in [" Linux", " GNU/Linux", " OS", " Linux"]:
             if name.endswith(suffix):
-                name = name[:-len(suffix)]
+                name = name[: -len(suffix)]
         labels.append(name)
         data.append(s[1])
 
-    return render_template('stats.html',
-                           users_awesome=users_awesome,
-                           users_haters=users_haters,
-                           results_stats=stats,
-                           results_viewed=viewed,
-                           results_submitted=submitted,
-                           labels=labels, data=data)
+    return render_template(
+        "stats.html",
+        users_awesome=users_awesome,
+        users_haters=users_haters,
+        results_stats=stats,
+        results_viewed=viewed,
+        results_submitted=submitted,
+        labels=labels,
+        data=data,
+    )
+
 
-@app.route('/admin/review/<int:review_id>')
+@app.route("/admin/review/<int:review_id>")
 @login_required
 def admin_show_review(review_id):
     """
@@ -272,8 +340,8 @@ def admin_show_review(review_id):
     """
     review = db.session.query(Review).filter(Review.review_id == review_id).first()
     if not review:
-        flash('No review with that ID')
-        return redirect(url_for('.odrs_index'))
+        flash("No review with that ID")
+        return redirect(url_for(".odrs_index"))
 
     # has the user already voted
     if current_user.user:
@@ -283,48 +351,50 @@ def admin_show_review(review_id):
 
     # does the review contain any banned keywords
     matched_taboos = review.matches_taboos(_get_taboos_for_locale(review.locale))
-    return render_template('show.html', r=review,
-                           vote_exists=vote,
-                           matched_taboos=matched_taboos)
+    return render_template(
+        "show.html", r=review, vote_exists=vote, matched_taboos=matched_taboos
+    )
+
 
-@app.route('/admin/modify/<int:review_id>', methods=['POST'])
+@app.route("/admin/modify/<int:review_id>", methods=["POST"])
 @login_required
 def admin_modify(review_id):
-    """ Change details about a review """
+    """Change details about a review"""
     review = db.session.query(Review).filter(Review.review_id == review_id).first()
     if not review:
-        flash('No review with that ID')
-        return redirect(url_for('.odrs_index'))
-    if 'distro' in request.form:
-        review.distro = request.form['distro']
-    if 'locale' in request.form:
-        review.locale = request.form['locale']
-    if 'user_display' in request.form:
-        if len(request.form['user_display']) == 0:
+        flash("No review with that ID")
+        return redirect(url_for(".odrs_index"))
+    if "distro" in request.form:
+        review.distro = request.form["distro"]
+    if "locale" in request.form:
+        review.locale = request.form["locale"]
+    if "user_display" in request.form:
+        if len(request.form["user_display"]) == 0:
             review.user_display = None
         else:
-            review.user_display = request.form['user_display']
-    if 'description' in request.form:
-        review.description = request.form['description']
-    if 'summary' in request.form:
-        review.summary = request.form['summary']
-    if 'version' in request.form:
-        review.version = request.form['version']
+            review.user_display = request.form["user_display"]
+    if "description" in request.form:
+        review.description = request.form["description"]
+    if "summary" in request.form:
+        review.summary = request.form["summary"]
+    if "version" in request.form:
+        review.version = request.form["version"]
     db.session.commit()
-    return redirect(url_for('.admin_show_review', review_id=review_id))
+    return redirect(url_for(".admin_show_review", review_id=review_id))
 
-@app.route('/admin/user_ban/<user_hash>')
+
+@app.route("/admin/user_ban/<user_hash>")
 @login_required
 def admin_user_ban(user_hash):
-    """ Change details about a review """
+    """Change details about a review"""
     # security check
     if not current_user.is_admin:
-        flash('Unable to ban user as non-admin', 'error')
-        return redirect(url_for('.odrs_index'))
+        flash("Unable to ban user as non-admin", "error")
+        return redirect(url_for(".odrs_index"))
     user = db.session.query(User).filter(User.user_hash == user_hash).first()
     if not user:
-        flash('No user with that user_hash')
-        return redirect(url_for('.odrs_index'))
+        flash("No user with that user_hash")
+        return redirect(url_for(".odrs_index"))
     user.is_banned = True
 
     # delete any of the users reviews
@@ -332,84 +402,91 @@ def admin_user_ban(user_hash):
     for review in user.reviews:
         db.session.delete(review)
     db.session.commit()
-    flash('Banned user and deleted {} reviews'.format(nr_delete))
-    return redirect(url_for('.odrs_show_reported'))
+    flash("Banned user and deleted {} reviews".format(nr_delete))
+    return redirect(url_for(".odrs_show_reported"))
+
 
-@app.route('/admin/unreport/<int:review_id>')
+@app.route("/admin/unreport/<int:review_id>")
 @login_required
 def admin_unreport(review_id):
-    """ Unreport a perfectly valid review """
+    """Unreport a perfectly valid review"""
     review = db.session.query(Review).filter(Review.review_id == review_id).first()
     if not review:
-        flash('No review with that ID')
-        return redirect(url_for('.odrs_index'))
+        flash("No review with that ID")
+        return redirect(url_for(".odrs_index"))
     review.reported = 0
     db.session.commit()
-    flash('Review unreported')
-    return redirect(url_for('.admin_show_review', review_id=review_id))
+    flash("Review unreported")
+    return redirect(url_for(".admin_show_review", review_id=review_id))
 
-@app.route('/admin/unremove/<int:review_id>')
+
+@app.route("/admin/unremove/<int:review_id>")
 @login_required
 def admin_unremove(review_id):
-    """ Unreport a perfectly valid review """
+    """Unreport a perfectly valid review"""
     review = db.session.query(Review).filter(Review.review_id == review_id).first()
     if not review:
-        flash('No review with that ID')
-        return redirect(url_for('.odrs_index'))
+        flash("No review with that ID")
+        return redirect(url_for(".odrs_index"))
     review.date_deleted = None
     db.session.commit()
-    flash('Review unremoved')
-    return redirect(url_for('.admin_show_review', review_id=review_id))
+    flash("Review unremoved")
+    return redirect(url_for(".admin_show_review", review_id=review_id))
+
 
-@app.route('/admin/englishify/<int:review_id>')
+@app.route("/admin/englishify/<int:review_id>")
 @login_required
 def admin_englishify(review_id):
-    """ Marks a review as writen in English """
+    """Marks a review as writen in English"""
     review = db.session.query(Review).filter(Review.review_id == review_id).first()
     if not review:
-        flash('No review with that ID')
-        return redirect(url_for('.odrs_index'))
-    parts = review.locale.split('_')
+        flash("No review with that ID")
+        return redirect(url_for(".odrs_index"))
+    parts = review.locale.split("_")
     if len(parts) == 1:
-        review.locale = 'en'
+        review.locale = "en"
     else:
-        review.locale = 'en_' + parts[1]
+        review.locale = "en_" + parts[1]
     db.session.commit()
-    return redirect(url_for('.admin_show_review', review_id=review_id))
+    return redirect(url_for(".admin_show_review", review_id=review_id))
 
-@app.route('/admin/anonify/<int:review_id>')
+
+@app.route("/admin/anonify/<int:review_id>")
 @login_required
 def admin_anonify(review_id):
-    """ Removes the username from the review """
+    """Removes the username from the review"""
     review = db.session.query(Review).filter(Review.review_id == review_id).first()
     if not review:
-        flash('No review with that ID')
-        return redirect(url_for('.odrs_index'))
+        flash("No review with that ID")
+        return redirect(url_for(".odrs_index"))
     review.user_display = None
     db.session.commit()
-    return redirect(url_for('.admin_show_review', review_id=review_id))
+    return redirect(url_for(".admin_show_review", review_id=review_id))
+
 
-@app.route('/admin/delete/<review_id>/force')
+@app.route("/admin/delete/<review_id>/force")
 @login_required
 def admin_delete_force(review_id):
-    """ Delete a review """
+    """Delete a review"""
     review = db.session.query(Review).filter(Review.review_id == review_id).first()
     if not review:
-        flash('No review with that ID')
-        return redirect(url_for('.odrs_index'))
+        flash("No review with that ID")
+        return redirect(url_for(".odrs_index"))
     db.session.delete(review)
     db.session.commit()
-    flash('Deleted review')
-    return redirect(url_for('.odrs_show_reported'))
+    flash("Deleted review")
+    return redirect(url_for(".odrs_show_reported"))
 
-@app.route('/admin/delete/<review_id>')
+
+@app.route("/admin/delete/<review_id>")
 @login_required
 def admin_delete(review_id):
-    """ Ask for confirmation to delete a review """
-    return render_template('delete.html', review_id=review_id)
+    """Ask for confirmation to delete a review"""
+    return render_template("delete.html", review_id=review_id)
+
 
-@app.route('/admin/show/all')
-@app.route('/admin/show/all/page/<int:page>')
+@app.route("/admin/show/all")
+@app.route("/admin/show/all/page/<int:page>")
 @login_required
 def admin_show_all(page=1):
     """
@@ -422,48 +499,55 @@ def admin_show_all(page=1):
 
     reviews_per_page = 20
     pagination = Pagination(page, reviews_per_page, len(reviews))
-    reviews = reviews[(page-1) * reviews_per_page:page * reviews_per_page]
-    return render_template('show-all.html',
-                           pagination=pagination,
-                           reviews=reviews)
+    reviews = reviews[(page - 1) * reviews_per_page : page * reviews_per_page]
+    return render_template("show-all.html", pagination=pagination, reviews=reviews)
+
 
 def _review_filter_keys(keys):
     cond = []
     for key in keys:
-        cond.append(Review.user_display.like('%{}%'.format(key)))
-        cond.append(Review.summary.like('%{}%'.format(key)))
-        cond.append(Review.description.like('%{}%'.format(key)))
+        cond.append(Review.user_display.like("%{}%".format(key)))
+        cond.append(Review.summary.like("%{}%".format(key)))
+        cond.append(Review.description.like("%{}%".format(key)))
     return or_(*cond)
 
-@app.route('/admin/search')
-@app.route('/admin/search/<int:max_results>')
+
+@app.route("/admin/search")
+@app.route("/admin/search/<int:max_results>")
 def admin_search(max_results=19):
 
     # no search results
-    if 'value' not in request.args:
-        return render_template('search.html')
-
-    keys = request.args['value'].split(' ')
-    reviews = db.session.query(Review).\
-                    filter(_review_filter_keys(keys)).\
-                    order_by(Review.date_created.desc()).\
-                    limit(max_results).all()
-    return render_template('show-all.html',
-                           reviews=reviews)
-
-@app.route('/admin/show/reported')
-@app.route('/admin/show/reported/<int:limit>')
+    if "value" not in request.args:
+        return render_template("search.html")
+
+    keys = request.args["value"].split(" ")
+    reviews = (
+        db.session.query(Review)
+        .filter(_review_filter_keys(keys))
+        .order_by(Review.date_created.desc())
+        .limit(max_results)
+        .all()
+    )
+    return render_template("show-all.html", reviews=reviews)
+
+
+@app.route("/admin/show/reported")
+@app.route("/admin/show/reported/<int:limit>")
 @login_required
 def odrs_show_reported(limit=1):
     """
     Return all the reported reviews on the server as HTML.
     """
-    reviews = db.session.query(Review).\
-                    filter(Review.reported >= limit).\
-                    order_by(Review.date_created.desc()).all()
-    return render_template('show-all.html', reviews=reviews)
+    reviews = (
+        db.session.query(Review)
+        .filter(Review.reported >= limit)
+        .order_by(Review.date_created.desc())
+        .all()
+    )
+    return render_template("show-all.html", reviews=reviews)
+
 
-@app.route('/admin/show/user/<user_hash>')
+@app.route("/admin/show/user/<user_hash>")
 @login_required
 def admin_show_user(user_hash):
     """
@@ -471,32 +555,38 @@ def admin_show_user(user_hash):
     """
     user = db.session.query(User).filter(User.user_hash == user_hash).first()
     if not user:
-        flash('No user with that user_hash')
-        return redirect(url_for('.admin_show_all'))
+        flash("No user with that user_hash")
+        return redirect(url_for(".admin_show_all"))
     reviews = db.session.query(Review).filter(Review.user_id == user.user_id).all()
-    return render_template('show-all.html', reviews=reviews)
+    return render_template("show-all.html", reviews=reviews)
 
-@app.route('/admin/show/app/<app_id>')
+
+@app.route("/admin/show/app/<app_id>")
 @login_required
 def admin_show_app(app_id):
     """
     Return all the reviews from a user on the server as HTML.
     """
-    reviews = db.session.query(Review).\
-                    join(Component).\
-                    filter(Component.app_id == app_id).all()
-    return render_template('show-all.html', reviews=reviews)
+    reviews = (
+        db.session.query(Review)
+        .join(Component)
+        .filter(Component.app_id == app_id)
+        .all()
+    )
+    return render_template("show-all.html", reviews=reviews)
+
 
-@app.route('/admin/show/lang/<locale>')
+@app.route("/admin/show/lang/<locale>")
 @login_required
 def admin_show_lang(locale):
     """
     Return all the reviews from a user on the server as HTML.
     """
     reviews = db.session.query(Review).filter(Review.locale == locale).all()
-    return render_template('show-all.html', reviews=reviews)
+    return render_template("show-all.html", reviews=reviews)
 
-@app.route('/admin/moderators/all')
+
+@app.route("/admin/moderators/all")
 @login_required
 def admin_moderator_show_all():
     """
@@ -504,96 +594,111 @@ def admin_moderator_show_all():
     """
     # security check
     if not current_user.is_admin:
-        flash('Unable to show all moderators', 'error')
-        return redirect(url_for('.odrs_index'))
+        flash("Unable to show all moderators", "error")
+        return redirect(url_for(".odrs_index"))
     mods = db.session.query(Moderator).all()
-    return render_template('mods.html', mods=mods)
+    return render_template("mods.html", mods=mods)
+
 
-@app.route('/admin/moderator/add', methods=['GET', 'POST'])
+@app.route("/admin/moderator/add", methods=["GET", "POST"])
 @login_required
 def admin_moderator_add():
-    """ Add a moderator [ADMIN ONLY] """
+    """Add a moderator [ADMIN ONLY]"""
 
     # only accept form data
-    if request.method != 'POST':
-        return redirect(url_for('.profile'))
+    if request.method != "POST":
+        return redirect(url_for(".profile"))
 
     # security check
     if not current_user.is_admin:
-        flash('Unable to add moderator as non-admin', 'error')
-        return redirect(url_for('.odrs_index'))
+        flash("Unable to add moderator as non-admin", "error")
+        return redirect(url_for(".odrs_index"))
 
-    for key in ['username_new', 'password_new', 'display_name']:
+    for key in ["username_new", "password_new", "display_name"]:
         if not key in request.form:
-            flash('Unable to add moderator as {} missing'.format(key), 'error')
-            return redirect(url_for('.odrs_index'))
-    if db.session.query(Moderator).\
-            filter(Moderator.username == request.form['username_new']).first():
-        flash('Already a entry with that username', 'warning')
-        return redirect(url_for('.admin_moderator_show_all'))
+            flash("Unable to add moderator as {} missing".format(key), "error")
+            return redirect(url_for(".odrs_index"))
+    if (
+        db.session.query(Moderator)
+        .filter(Moderator.username == request.form["username_new"])
+        .first()
+    ):
+        flash("Already a entry with that username", "warning")
+        return redirect(url_for(".admin_moderator_show_all"))
 
     # verify password
-    password = request.form['password_new']
+    password = request.form["password_new"]
     if not _password_check(password):
-        return redirect(url_for('.admin_moderator_show_all'))
+        return redirect(url_for(".admin_moderator_show_all"))
 
     # verify username
-    username_new = request.form['username_new']
+    username_new = request.form["username_new"]
     if len(username_new) < 3:
-        flash('Username invalid', 'warning')
-        return redirect(url_for('.admin_moderator_show_all'))
+        flash("Username invalid", "warning")
+        return redirect(url_for(".admin_moderator_show_all"))
     if not _email_check(username_new):
-        flash('Invalid email address', 'warning')
-        return redirect(url_for('.admin_moderator_show_all'))
+        flash("Invalid email address", "warning")
+        return redirect(url_for(".admin_moderator_show_all"))
 
     # verify name
-    display_name = request.form['display_name']
+    display_name = request.form["display_name"]
     if len(display_name) < 3:
-        flash('Name invalid', 'warning')
-        return redirect(url_for('.admin_moderator_show_all'))
+        flash("Name invalid", "warning")
+        return redirect(url_for(".admin_moderator_show_all"))
 
     # verify username
     db.session.add(Moderator(username_new, password, display_name))
     db.session.commit()
-    flash('Added user')
-    return redirect(url_for('.admin_moderator_show_all'))
+    flash("Added user")
+    return redirect(url_for(".admin_moderator_show_all"))
 
-@app.route('/admin/moderator/<int:moderator_id>/admin')
+
+@app.route("/admin/moderator/<int:moderator_id>/admin")
 @login_required
 def odrs_moderator_show(moderator_id):
     """
     Shows an admin panel for a moderator
     """
     if moderator_id != current_user.moderator_id and not current_user.is_admin:
-        flash('Unable to show moderator information', 'error')
-        return redirect(url_for('.odrs_index'))
-    mod = db.session.query(Moderator).filter(Moderator.moderator_id == moderator_id).first()
+        flash("Unable to show moderator information", "error")
+        return redirect(url_for(".odrs_index"))
+    mod = (
+        db.session.query(Moderator)
+        .filter(Moderator.moderator_id == moderator_id)
+        .first()
+    )
     if not mod:
-        flash("No entry with moderator ID {}".format(moderator_id), 'warning')
-        return redirect(url_for('.admin_moderator_show_all'))
-    return render_template('modadmin.html', u=mod)
+        flash("No entry with moderator ID {}".format(moderator_id), "warning")
+        return redirect(url_for(".admin_moderator_show_all"))
+    return render_template("modadmin.html", u=mod)
+
 
-@app.route('/admin/moderator/<int:moderator_id>/delete')
+@app.route("/admin/moderator/<int:moderator_id>/delete")
 @login_required
 def admin_moderate_delete(moderator_id):
-    """ Delete a moderator """
+    """Delete a moderator"""
 
     # security check
     if not current_user.is_admin:
-        flash('Unable to delete moderator as not admin', 'error')
-        return redirect(url_for('.odrs_index'))
+        flash("Unable to delete moderator as not admin", "error")
+        return redirect(url_for(".odrs_index"))
 
     # check whether exists in database
-    mod = db.session.query(Moderator).filter(Moderator.moderator_id == moderator_id).first()
+    mod = (
+        db.session.query(Moderator)
+        .filter(Moderator.moderator_id == moderator_id)
+        .first()
+    )
     if not mod:
-        flash("No entry with moderator ID {}".format(moderator_id), 'warning')
-        return redirect(url_for('.admin_moderator_show_all'))
+        flash("No entry with moderator ID {}".format(moderator_id), "warning")
+        return redirect(url_for(".admin_moderator_show_all"))
     db.session.delete(mod)
     db.session.commit()
-    flash('Deleted user')
-    return redirect(url_for('.admin_moderator_show_all'))
+    flash("Deleted user")
+    return redirect(url_for(".admin_moderator_show_all"))
+
 
-@app.route('/admin/taboo/all')
+@app.route("/admin/taboo/all")
 @login_required
 def admin_taboo_show_all():
     """
@@ -601,67 +706,80 @@ def admin_taboo_show_all():
     """
     # security check
     if not current_user.is_admin:
-        flash('Unable to show all taboos', 'error')
-        return redirect(url_for('.odrs_index'))
-    taboos = db.session.query(Taboo).\
-                order_by(Taboo.locale.asc()).\
-                order_by(Taboo.value.asc()).all()
-    return render_template('taboos.html', taboos=taboos)
-
-@app.route('/admin/taboo/add', methods=['GET', 'POST'])
+        flash("Unable to show all taboos", "error")
+        return redirect(url_for(".odrs_index"))
+    taboos = (
+        db.session.query(Taboo)
+        .order_by(Taboo.locale.asc())
+        .order_by(Taboo.value.asc())
+        .all()
+    )
+    return render_template("taboos.html", taboos=taboos)
+
+
+@app.route("/admin/taboo/add", methods=["GET", "POST"])
 @login_required
 def admin_taboo_add():
-    """ Add a taboo [ADMIN ONLY] """
+    """Add a taboo [ADMIN ONLY]"""
 
     # only accept form data
-    if request.method != 'POST':
-        return redirect(url_for('.admin_taboo_show_all'))
+    if request.method != "POST":
+        return redirect(url_for(".admin_taboo_show_all"))
 
     # security check
     if not current_user.is_admin:
-        flash('Unable to add taboo as non-admin', 'error')
-        return redirect(url_for('.odrs_index'))
+        flash("Unable to add taboo as non-admin", "error")
+        return redirect(url_for(".odrs_index"))
 
-    for key in ['locale', 'value', 'description', 'severity']:
+    for key in ["locale", "value", "description", "severity"]:
         if not key in request.form:
-            flash('Unable to add taboo as {} missing'.format(key), 'error')
-            return redirect(url_for('.odrs_index'))
-    if db.session.query(Taboo).\
-            filter(Taboo.locale == request.form['locale']).\
-            filter(Taboo.value == request.form['value']).first():
-        flash('Already added that taboo', 'warning')
-        return redirect(url_for('.admin_taboo_show_all'))
+            flash("Unable to add taboo as {} missing".format(key), "error")
+            return redirect(url_for(".odrs_index"))
+    if (
+        db.session.query(Taboo)
+        .filter(Taboo.locale == request.form["locale"])
+        .filter(Taboo.value == request.form["value"])
+        .first()
+    ):
+        flash("Already added that taboo", "warning")
+        return redirect(url_for(".admin_taboo_show_all"))
 
     # verify username
-    db.session.add(Taboo(request.form['locale'],
-                         request.form['value'],
-                         request.form['description'],
-                         int(request.form['severity'])))
+    db.session.add(
+        Taboo(
+            request.form["locale"],
+            request.form["value"],
+            request.form["description"],
+            int(request.form["severity"]),
+        )
+    )
     db.session.commit()
-    flash('Added taboo')
-    return redirect(url_for('.admin_taboo_show_all'))
+    flash("Added taboo")
+    return redirect(url_for(".admin_taboo_show_all"))
 
-@app.route('/admin/taboo/<int:taboo_id>/delete')
+
+@app.route("/admin/taboo/<int:taboo_id>/delete")
 @login_required
 def admin_taboo_delete(taboo_id):
-    """ Delete an taboo """
+    """Delete an taboo"""
 
     # security check
     if not current_user.is_admin:
-        flash('Unable to delete taboo as not admin', 'error')
-        return redirect(url_for('.odrs_index'))
+        flash("Unable to delete taboo as not admin", "error")
+        return redirect(url_for(".odrs_index"))
 
     # check whether exists in database
     taboo = db.session.query(Taboo).filter(Taboo.taboo_id == taboo_id).first()
     if not taboo:
-        flash("No taboo with ID {}".format(taboo_id), 'warning')
-        return redirect(url_for('.admin_taboo_show_all'))
+        flash("No taboo with ID {}".format(taboo_id), "warning")
+        return redirect(url_for(".admin_taboo_show_all"))
     db.session.delete(taboo)
     db.session.commit()
-    flash('Deleted taboo')
-    return redirect(url_for('.admin_taboo_show_all'))
+    flash("Deleted taboo")
+    return redirect(url_for(".admin_taboo_show_all"))
+
 
-@app.route('/admin/component/all')
+@app.route("/admin/component/all")
 @login_required
 def admin_component_show_all():
     """
@@ -669,14 +787,18 @@ def admin_component_show_all():
     """
     # security check
     if not current_user.is_admin:
-        flash('Unable to show all components', 'error')
-        return redirect(url_for('.odrs_index'))
-    components = db.session.query(Component).\
-                order_by(Component.app_id.asc()).\
-                order_by(Component.review_cnt.asc()).all()
-    return render_template('components.html', components=components)
-
-@app.route('/admin/component/join/<component_id_parent>/<component_id_child>')
+        flash("Unable to show all components", "error")
+        return redirect(url_for(".odrs_index"))
+    components = (
+        db.session.query(Component)
+        .order_by(Component.app_id.asc())
+        .order_by(Component.review_cnt.asc())
+        .all()
+    )
+    return render_template("components.html", components=components)
+
+
+@app.route("/admin/component/join/<component_id_parent>/<component_id_child>")
 @login_required
 def admin_component_join(component_id_parent, component_id_child):
     """
@@ -684,62 +806,79 @@ def admin_component_join(component_id_parent, component_id_child):
     """
     # security check
     if not current_user.is_admin:
-        flash('Unable to join components', 'error')
-        return redirect(url_for('.odrs_index'))
-    parent = db.session.query(Component).filter(Component.app_id == component_id_parent).first()
+        flash("Unable to join components", "error")
+        return redirect(url_for(".odrs_index"))
+    parent = (
+        db.session.query(Component)
+        .filter(Component.app_id == component_id_parent)
+        .first()
+    )
     if not parent:
-        flash('No parent component found', 'warning')
-        return redirect(url_for('.admin_component_show_all'))
-    child = db.session.query(Component).filter(Component.app_id == component_id_child).first()
+        flash("No parent component found", "warning")
+        return redirect(url_for(".admin_component_show_all"))
+    child = (
+        db.session.query(Component)
+        .filter(Component.app_id == component_id_child)
+        .first()
+    )
     if not child:
-        flash('No child component found', 'warning')
-        return redirect(url_for('.admin_component_show_all'))
+        flash("No child component found", "warning")
+        return redirect(url_for(".admin_component_show_all"))
     if parent.component_id == child.component_id:
-        flash('Parent and child components were the same', 'warning')
-        return redirect(url_for('.admin_component_show_all'))
+        flash("Parent and child components were the same", "warning")
+        return redirect(url_for(".admin_component_show_all"))
     if parent.component_id == child.component_id_parent:
-        flash('Parent and child already set up', 'warning')
-        return redirect(url_for('.admin_component_show_all'))
+        flash("Parent and child already set up", "warning")
+        return redirect(url_for(".admin_component_show_all"))
 
     # return best message
     adopted = parent.adopt(child)
     db.session.commit()
     if adopted:
-        flash('Joined components, adopting {} additional components'.format(adopted), 'info')
+        flash(
+            "Joined components, adopting {} additional components".format(adopted),
+            "info",
+        )
     else:
-        flash('Joined components', 'info')
-    return redirect(url_for('.admin_component_show_all'))
+        flash("Joined components", "info")
+    return redirect(url_for(".admin_component_show_all"))
 
 
-@app.route('/admin/component/join', methods=['POST'])
+@app.route("/admin/component/join", methods=["POST"])
 @login_required
 def admin_component_join2():
-    """ Change details about the any user """
+    """Change details about the any user"""
 
     # security check
     if not current_user.is_admin:
-        flash('Unable to join components', 'error')
-        return redirect(url_for('.odrs_index'))
+        flash("Unable to join components", "error")
+        return redirect(url_for(".odrs_index"))
 
     # set each thing in turn
     parent = None
     children = []
     for key in request.form:
-        if key == 'parent':
-            parent = db.session.query(Component).\
-                        filter(Component.app_id == request.form[key]).first()
-        if key == 'child':
+        if key == "parent":
+            parent = (
+                db.session.query(Component)
+                .filter(Component.app_id == request.form[key])
+                .first()
+            )
+        if key == "child":
             for component_id in request.form.getlist(key):
-                child = db.session.query(Component).\
-                            filter(Component.app_id == component_id).first()
+                child = (
+                    db.session.query(Component)
+                    .filter(Component.app_id == component_id)
+                    .first()
+                )
                 if child:
                     children.append(child)
     if not parent:
-        flash('No parent component found', 'warning')
-        return redirect(url_for('.admin_component_show_all'))
+        flash("No parent component found", "warning")
+        return redirect(url_for(".admin_component_show_all"))
     if not children:
-        flash('No child components found', 'warning')
-        return redirect(url_for('.admin_component_show_all'))
+        flash("No child components found", "warning")
+        return redirect(url_for(".admin_component_show_all"))
 
     # adopt each child
     adopted = 0
@@ -750,91 +889,101 @@ def admin_component_join2():
         adopted += parent.adopt(child)
     db.session.commit()
     if adopted:
-        flash('Joined {} components, '
-              'adopting {} additional components'.format(len(children),
-                                                         adopted), 'info')
+        flash(
+            "Joined {} components, "
+            "adopting {} additional components".format(len(children), adopted),
+            "info",
+        )
     else:
-        flash('Joined {} components'.format(len(children)), 'info')
-    return redirect(url_for('.admin_component_show_all'))
+        flash("Joined {} components".format(len(children)), "info")
+    return redirect(url_for(".admin_component_show_all"))
 
-@app.route('/admin/component/delete/<int:component_id>')
+
+@app.route("/admin/component/delete/<int:component_id>")
 @login_required
 def admin_component_delete(component_id):
     """
     Delete component, and any reviews.
     """
     if not current_user.is_admin:
-        flash('Unable to delete component', 'error')
-        return redirect(url_for('.odrs_index'))
-    component = db.session.query(Component).filter(Component.component_id == component_id).first()
+        flash("Unable to delete component", "error")
+        return redirect(url_for(".odrs_index"))
+    component = (
+        db.session.query(Component)
+        .filter(Component.component_id == component_id)
+        .first()
+    )
     if not component:
-        flash('Unable to find component', 'error')
-        return redirect(url_for('.admin_component_show_all'))
+        flash("Unable to find component", "error")
+        return redirect(url_for(".admin_component_show_all"))
 
-    flash('Deleted component with {} reviews'.format(len(component.reviews)), 'info')
+    flash("Deleted component with {} reviews".format(len(component.reviews)), "info")
     db.session.delete(component)
     db.session.commit()
-    return redirect(url_for('.admin_component_show_all'))
+    return redirect(url_for(".admin_component_show_all"))
+
 
-@app.route('/admin/vote/<int:review_id>/<val_str>')
+@app.route("/admin/vote/<int:review_id>/<val_str>")
 @login_required
 def admin_vote(review_id, val_str):
-    """ Up or downvote an existing review by @val karma points """
+    """Up or downvote an existing review by @val karma points"""
     if not current_user.user:
-        flash('No user for moderator')
-        return redirect(url_for('.admin_show_review', review_id=review_id))
-    if val_str == 'up':
+        flash("No user for moderator")
+        return redirect(url_for(".admin_show_review", review_id=review_id))
+    if val_str == "up":
         val = 1
-    elif val_str == 'down':
+    elif val_str == "down":
         val = -1
-    elif val_str == 'meh':
+    elif val_str == "meh":
         val = 0
     else:
-        flash('Invalid vote value')
-        return redirect(url_for('.admin_show_review', review_id=review_id))
+        flash("Invalid vote value")
+        return redirect(url_for(".admin_show_review", review_id=review_id))
 
     # the user already has a review
     if _vote_exists(review_id, current_user.user_id):
-        flash('already voted on this app')
-        return redirect(url_for('.admin_show_review', review_id=review_id))
+        flash("already voted on this app")
+        return redirect(url_for(".admin_show_review", review_id=review_id))
 
     current_user.user.karma += val
     db.session.add(Vote(current_user.user_id, val, review_id=review_id))
     db.session.commit()
-    flash('Recorded vote')
-    return redirect(url_for('.admin_show_review', review_id=review_id))
+    flash("Recorded vote")
+    return redirect(url_for(".admin_show_review", review_id=review_id))
 
-@app.route('/admin/moderator/<int:moderator_id>/modify_by_admin', methods=['POST'])
+
+@app.route("/admin/moderator/<int:moderator_id>/modify_by_admin", methods=["POST"])
 @login_required
 def admin_user_modify_by_admin(moderator_id):
-    """ Change details about the any user """
+    """Change details about the any user"""
 
     # security check
     if moderator_id != current_user.moderator_id and not current_user.is_admin:
-        flash('Unable to modify user as non-admin', 'error')
-        return redirect(url_for('.odrs_index'))
-
-    mod = db.session.query(Moderator).filter(Moderator.moderator_id == moderator_id).first()
+        flash("Unable to modify user as non-admin", "error")
+        return redirect(url_for(".odrs_index"))
+
+    mod = (
+        db.session.query(Moderator)
+        .filter(Moderator.moderator_id == moderator_id)
+        .first()
+    )
     if not mod:
-        flash('moderator_id invalid', 'warning')
-        return redirect(url_for('.admin_moderator_show_all'))
+        flash("moderator_id invalid", "warning")
+        return redirect(url_for(".admin_moderator_show_all"))
 
     # set each thing in turn
-    mod.is_enabled = 'is_enabled' in request.form
-    mod.is_admin = 'is_admin' in request.form
-    for key in ['display_name',
-                'password',
-                'user_hash',
-                'locales']:
+    mod.is_enabled = "is_enabled" in request.form
+    mod.is_admin = "is_admin" in request.form
+    for key in ["display_name", "password", "user_hash", "locales"]:
         # unchecked checkbuttons are not included in the form data
         if key not in request.form:
             continue
 
         val = request.form[key]
         # don't set the optional password
-        if key == 'password' and len(val) == 0:
+        if key == "password" and len(val) == 0:
             continue
         setattr(mod, key, val)
     db.session.commit()
-    flash('Updated profile')
-    return redirect(url_for('.odrs_moderator_show', moderator_id=moderator_id))
+    flash("Updated profile")
+    return redirect(url_for(".odrs_moderator_show", moderator_id=moderator_id))
diff --git a/app_data/odrs/views_api.py b/app_data/odrs/views_api.py
index 1e3a8cf..7a5f27b 100644
--- a/app_data/odrs/views_api.py
+++ b/app_data/odrs/views_api.py
@@ -20,30 +20,46 @@ from odrs import app, db, csrf
 
 from .models import Review, User, Vote, Analytic, Component
 from .models import _vote_exists
-from .util import json_success, json_error, _locale_is_compatible, _eventlog_add, _get_user_key, 
_get_datestr_from_dt
-from .util import _sanitised_version, _sanitised_summary, _sanitised_description, _get_rating_for_component
+from .util import (
+    json_success,
+    json_error,
+    _locale_is_compatible,
+    _eventlog_add,
+    _get_user_key,
+    _get_datestr_from_dt,
+)
+from .util import (
+    _sanitised_version,
+    _sanitised_summary,
+    _sanitised_description,
+    _get_rating_for_component,
+)
 from .util import _get_taboos_for_locale
 
 ODRS_REPORTED_CNT = 2
 
+
 def _get_client_address():
-    """ Gets user IP address """
-    if request.headers.getlist('X-Forwarded-For'):
-        return request.headers.getlist('X-Forwarded-For')[0]
+    """Gets user IP address"""
+    if request.headers.getlist("X-Forwarded-For"):
+        return request.headers.getlist("X-Forwarded-For")[0]
     return request.remote_addr
 
+
 def _wilson(ku, kd):
     # algorithm from http://www.evanmiller.org/how-not-to-sort-by-average-rating.html
     wilson = 0
     if ku > 0 or kd > 0:
-        wilson = ((ku + 1.9208) / (ku + kd) -
-                  1.96 * math.sqrt((ku * kd) / (ku + kd) + 0.9604) /
-                  (ku + kd)) / (1 + 3.8416 / (ku + kd))
+        wilson = (
+            (ku + 1.9208) / (ku + kd)
+            - 1.96 * math.sqrt((ku * kd) / (ku + kd) + 0.9604) / (ku + kd)
+        ) / (1 + 3.8416 / (ku + kd))
         wilson *= 100
     return int(wilson)
 
+
 def _get_review_score(review, item):
-    """ Gets a review score given certain parameters """
+    """Gets a review score given certain parameters"""
     ku = review.karma_up
     kd = review.karma_down
 
@@ -56,123 +72,146 @@ def _get_review_score(review, item):
     # For every year that has passed and for a mismatched version or distro, we
     # add a penalty. Penalties will reduce the final score.
     penalties = months_old // 12
-    if review.version != item['version']:
+    if review.version != item["version"]:
         penalties += 1
-    if review.distro != item['distro']:
+    if review.distro != item["distro"]:
         penalties += 1
 
     w = _wilson(ku, kd)
     return max(0, w - penalties * 10)
 
+
 def _check_str(val):
-    """ Return with success if the summary and description """
-    if val.find('<') != -1:
+    """Return with success if the summary and description"""
+    if val.find("<") != -1:
         return False
-    if val.find('<') != -1:
+    if val.find("<") != -1:
         return False
     return True
 
-@app.route('/1.0/reviews/api/submit', methods=['POST'])
+
+@app.route("/1.0/reviews/api/submit", methods=["POST"])
 @csrf.exempt
 def api_submit():
     """
     Submits a new review.
     """
     try:
-        request_item = json.loads(request.data.decode('utf8'))
+        request_item = json.loads(request.data.decode("utf8"))
     except ValueError as e:
         return json_error(str(e))
-    required_fields = ['app_id', 'locale', 'summary', 'description',
-                       'user_hash', 'version', 'distro', 'rating',
-                       'user_display']
+    required_fields = [
+        "app_id",
+        "locale",
+        "summary",
+        "description",
+        "user_hash",
+        "version",
+        "distro",
+        "rating",
+        "user_display",
+    ]
     for key in required_fields:
         if not key in request_item:
-            return json_error('invalid data, expected %s' % key)
+            return json_error("invalid data, expected %s" % key)
         if not request_item[key]:
-            return json_error('missing data, expected %s' % key)
+            return json_error("missing data, expected %s" % key)
 
     # check format
-    if not len(request_item['user_hash']) == 40:
-        return json_error('the user_hash is invalid')
+    if not len(request_item["user_hash"]) == 40:
+        return json_error("the user_hash is invalid")
 
     # check fields for markup and length
-    if len(request_item['summary']) > 70:
-        return json_error('summary is too long')
-    if len(request_item['description']) > 3000:
-        return json_error('description is too long')
-    for key in ['summary', 'description']:
+    if len(request_item["summary"]) > 70:
+        return json_error("summary is too long")
+    if len(request_item["description"]) > 3000:
+        return json_error("description is too long")
+    for key in ["summary", "description"]:
         if not _check_str(request_item[key]):
-            return json_error('%s is not a valid string' % key)
+            return json_error("%s is not a valid string" % key)
 
     # check user has not been banned
-    user = db.session.query(User).filter(User.user_hash == request_item['user_hash']).first()
+    user = (
+        db.session.query(User)
+        .filter(User.user_hash == request_item["user_hash"])
+        .first()
+    )
     if user:
         if user.is_banned:
-            return json_error('account has been disabled due to abuse')
+            return json_error("account has been disabled due to abuse")
     else:
-        user = User(request_item['user_hash'])
+        user = User(request_item["user_hash"])
         db.session.add(user)
 
     # user has already reviewed
-    if db.session.query(Review).\
-            join(Component).\
-            filter(Component.app_id == request_item['app_id']).\
-            filter(Review.user_id == user.user_id).first():
-        _eventlog_add(_get_client_address(),
-                      user.user_id,
-                      request_item['app_id'],
-                      'already reviewed')
-        return json_error('already reviewed this app')
+    if (
+        db.session.query(Review)
+        .join(Component)
+        .filter(Component.app_id == request_item["app_id"])
+        .filter(Review.user_id == user.user_id)
+        .first()
+    ):
+        _eventlog_add(
+            _get_client_address(),
+            user.user_id,
+            request_item["app_id"],
+            "already reviewed",
+        )
+        return json_error("already reviewed this app")
 
     # component definately exists now!
-    component = db.session.query(Component).filter(Component.app_id == request_item['app_id']).first()
+    component = (
+        db.session.query(Component)
+        .filter(Component.app_id == request_item["app_id"])
+        .first()
+    )
     if component:
         component.review_cnt += 1
     else:
-        component = Component(request_item['app_id'])
+        component = Component(request_item["app_id"])
         db.session.add(component)
         db.session.commit()
 
     # create new
     review = Review()
-    review.locale = request_item['locale']
-    review.summary = _sanitised_summary(request_item['summary'])
+    review.locale = request_item["locale"]
+    review.summary = _sanitised_summary(request_item["summary"])
     if len(review.summary) < 2:
-        return json_error('summary is too short')
-    review.description = _sanitised_description(request_item['description'])
+        return json_error("summary is too short")
+    review.description = _sanitised_description(request_item["description"])
     if len(review.description) < 2:
-        return json_error('description is too short')
+        return json_error("description is too short")
     review.user_id = user.user_id
-    review.version = _sanitised_version(request_item['version'])
-    review.distro = request_item['distro']
-    review.rating = request_item['rating']
+    review.version = _sanitised_version(request_item["version"])
+    review.distro = request_item["distro"]
+    review.rating = request_item["rating"]
     review.user_addr = _get_client_address()
     review.component_id = component.component_id
 
     # check if valid
-    user_display_ignore = ['root',
-                           'Administrator',
-                           'Live System User',
-                           'user',
-                           'Unknown']
-    if request_item['user_display'] not in user_display_ignore:
-        review.user_display = request_item['user_display']
+    user_display_ignore = [
+        "root",
+        "Administrator",
+        "Live System User",
+        "user",
+        "Unknown",
+    ]
+    if request_item["user_display"] not in user_display_ignore:
+        review.user_display = request_item["user_display"]
 
     # contains taboos
     if review.matches_taboos(_get_taboos_for_locale(review.locale)):
         review.reported = 5
 
     # log and add
-    _eventlog_add(_get_client_address(),
-                  review.user_id,
-                  component.app_id,
-                  'reviewed')
+    _eventlog_add(_get_client_address(), review.user_id, component.app_id, "reviewed")
     db.session.add(review)
     db.session.commit()
     return json_success()
 
-@app.route('/1.0/reviews/api/app/<app_id>/<user_hash>')
-@app.route('/1.0/reviews/api/app/<app_id>')
+
+@app.route("/1.0/reviews/api/app/<app_id>/<user_hash>")
+@app.route("/1.0/reviews/api/app/<app_id>")
 def api_show_app(app_id, user_hash=None):
     """
     Return details about an application.
@@ -180,73 +219,86 @@ def api_show_app(app_id, user_hash=None):
     reviews = []
     component = db.session.query(Component).filter(Component.app_id == app_id).first()
     if component:
-        reviews = db.session.query(Review).\
-                        join(Component).\
-                        filter(Component.app_id.in_(component.app_ids)).\
-                        filter(Review.reported < ODRS_REPORTED_CNT).\
-                        order_by(Review.date_created.desc()).all()
+        reviews = (
+            db.session.query(Review)
+            .join(Component)
+            .filter(Component.app_id.in_(component.app_ids))
+            .filter(Review.reported < ODRS_REPORTED_CNT)
+            .order_by(Review.date_created.desc())
+            .all()
+        )
     items = [review.asdict(user_hash) for review in reviews]
-    dat = json.dumps(items, sort_keys=True, indent=4, separators=(',', ': '))
-    return Response(response=dat,
-                    status=200, \
-                    mimetype='application/json')
+    dat = json.dumps(items, sort_keys=True, indent=4, separators=(",", ": "))
+    return Response(response=dat, status=200, mimetype="application/json")
+
 
-@app.route('/1.0/reviews/api/fetch', methods=['POST'])
+@app.route("/1.0/reviews/api/fetch", methods=["POST"])
 @csrf.exempt
 def api_fetch():
     """
     Return details about an application.
     """
     try:
-        request_item = json.loads(request.data.decode('utf8'))
+        request_item = json.loads(request.data.decode("utf8"))
     except ValueError as e:
         return json_error(str(e))
-    for key in ['app_id', 'user_hash', 'locale', 'distro', 'limit', 'version']:
+    for key in ["app_id", "user_hash", "locale", "distro", "limit", "version"]:
         if not key in request_item:
-            return json_error('invalid data, expected %s' % key)
+            return json_error("invalid data, expected %s" % key)
         if not request_item[key] and request_item[key] != 0:
-            return json_error('missing data, expected %s' % key)
+            return json_error("missing data, expected %s" % key)
 
     # check format
-    if not len(request_item['user_hash']) == 40:
-        return json_error('the user_hash is invalid')
+    if not len(request_item["user_hash"]) == 40:
+        return json_error("the user_hash is invalid")
 
     # increments the fetch count on one specific application
     datestr = _get_datestr_from_dt(datetime.date.today())
-    stmt = insert(Analytic).values(datestr=datestr, app_id=request_item['app_id'])
-    if db.session.bind.dialect.name != 'sqlite': # pylint: disable=no-member
+    stmt = insert(Analytic).values(datestr=datestr, app_id=request_item["app_id"])
+    if db.session.bind.dialect.name != "sqlite":  # pylint: disable=no-member
         stmt_ondupe = stmt.on_duplicate_key_update(fetch_cnt=Analytic.fetch_cnt + 1)
     else:
         stmt_ondupe = stmt
     try:
-        db.session.execute(stmt_ondupe) # pylint: disable=no-member
+        db.session.execute(stmt_ondupe)  # pylint: disable=no-member
         db.session.commit()
     except IntegrityError as e:
-        print('ignoring: {}'.format(str(e)))
+        print("ignoring: {}".format(str(e)))
 
     # increment the counter for the stats
-    component = db.session.query(Component).filter(Component.app_id == request_item['app_id']).first()
+    component = (
+        db.session.query(Component)
+        .filter(Component.app_id == request_item["app_id"])
+        .first()
+    )
     if component:
         component.fetch_cnt += 1
         db.session.commit()
 
     # also add any compat IDs
-    app_ids = [request_item['app_id']]
-    if 'compat_ids' in request_item:
-        app_ids.extend(request_item['compat_ids'])
+    app_ids = [request_item["app_id"]]
+    if "compat_ids" in request_item:
+        app_ids.extend(request_item["compat_ids"])
     if component:
         for app_id in component.app_ids:
             if app_id not in app_ids:
                 app_ids.append(app_id)
-    reviews = db.session.query(Review).\
-                    join(Component).\
-                    filter(Component.app_id.in_(app_ids)).\
-                    filter(Review.reported < ODRS_REPORTED_CNT).all()
+    reviews = (
+        db.session.query(Review)
+        .join(Component)
+        .filter(Component.app_id.in_(app_ids))
+        .filter(Review.reported < ODRS_REPORTED_CNT)
+        .all()
+    )
 
     # if user does not exist then create
-    user = db.session.query(User).filter(User.user_hash == request_item['user_hash']).first()
+    user = (
+        db.session.query(User)
+        .filter(User.user_hash == request_item["user_hash"])
+        .first()
+    )
     if not user:
-        user = User(user_hash=request_item['user_hash'])
+        user = User(user_hash=request_item["user_hash"])
         db.session.add(user)
         db.session.commit()
 
@@ -255,50 +307,53 @@ def api_fetch():
     for review in reviews:
 
         # the user isn't going to be able to read this
-        if not _locale_is_compatible(review.locale, request_item['locale']):
+        if not _locale_is_compatible(review.locale, request_item["locale"]):
             continue
 
         # return all results
-        item_new = review.asdict(request_item['user_hash'])
-        item_new['score'] = _get_review_score(review, request_item)
-        item_new['user_skey'] = _get_user_key(request_item['user_hash'], item_new['app_id'])
+        item_new = review.asdict(request_item["user_hash"])
+        item_new["score"] = _get_review_score(review, request_item)
+        item_new["user_skey"] = _get_user_key(
+            request_item["user_hash"], item_new["app_id"]
+        )
 
         # the UI can hide the vote buttons on reviews already voted on
         if _vote_exists(review.review_id, user.user_id):
-            item_new['vote_id'] = 1
+            item_new["vote_id"] = 1
 
         items_new.append(item_new)
 
     # fake something so the user can get the user_skey
     if len(items_new) == 0:
         item_new = {}
-        item_new['score'] = 0
-        item_new['app_id'] = request_item['app_id']
-        item_new['user_hash'] = request_item['user_hash']
-        item_new['user_skey'] = _get_user_key(request_item['user_hash'], item_new['app_id'])
+        item_new["score"] = 0
+        item_new["app_id"] = request_item["app_id"]
+        item_new["user_hash"] = request_item["user_hash"]
+        item_new["user_skey"] = _get_user_key(
+            request_item["user_hash"], item_new["app_id"]
+        )
         items_new.append(item_new)
 
     # sort and cut to limit
-    items_new.sort(key=lambda item: item['score'], reverse=True)
+    items_new.sort(key=lambda item: item["score"], reverse=True)
 
-    if request_item['limit'] == 0:
+    if request_item["limit"] == 0:
         limit = 50
     else:
-        limit = request_item.get('limit', -1)
+        limit = request_item.get("limit", -1)
 
-    start = request_item.get('start', 0)
-    items_new = items_new[start : start+limit]
+    start = request_item.get("start", 0)
+    items_new = items_new[start : start + limit]
 
-    dat = json.dumps(items_new, sort_keys=True, indent=4, separators=(',', ': '))
-    response = Response(response=dat,
-                        status=200, \
-                        mimetype='application/json')
+    dat = json.dumps(items_new, sort_keys=True, indent=4, separators=(",", ": "))
+    response = Response(response=dat, status=200, mimetype="application/json")
     response.cache_control.private = True
     response.add_etag()
     return response.make_conditional(request)
 
-@app.route('/1.0/reviews/api/moderate/<user_hash>')
-@app.route('/1.0/reviews/api/moderate/<user_hash>/<locale>')
+
+@app.route("/1.0/reviews/api/moderate/<user_hash>")
+@app.route("/1.0/reviews/api/moderate/<user_hash>/<locale>")
 def api_moderate(user_hash, locale=None):
     """
     Return all the reviews on the server the user can moderate.
@@ -307,7 +362,7 @@ def api_moderate(user_hash, locale=None):
     items = []
     user = db.session.query(User).filter(User.user_hash == user_hash).first()
     if not user:
-        return json_error('no user for {}'.format(user_hash))
+        return json_error("no user for {}".format(user_hash))
     for review in db.session.query(Review).all():
         if locale and not _locale_is_compatible(review.locale, locale):
             continue
@@ -316,68 +371,90 @@ def api_moderate(user_hash, locale=None):
         items.append(review.asdict(user_hash))
         if len(items) > 250:
             break
-    dat = json.dumps(items, sort_keys=True, indent=4, separators=(',', ': '))
-    return Response(response=dat,
-                    status=200, \
-                    mimetype='application/json')
+    dat = json.dumps(items, sort_keys=True, indent=4, separators=(",", ": "))
+    return Response(response=dat, status=200, mimetype="application/json")
+
 
 def _vote(val):
     """
     Up or downvote an existing review by @val karma points.
     """
     try:
-        request_item = json.loads(request.data.decode('utf8'))
+        request_item = json.loads(request.data.decode("utf8"))
     except ValueError as e:
         return json_error(str(e))
-    for key in ['review_id', 'app_id', 'user_hash', 'user_skey']:
+    for key in ["review_id", "app_id", "user_hash", "user_skey"]:
         if not key in request_item:
-            return json_error('invalid data, required %s' % key)
+            return json_error("invalid data, required %s" % key)
         if request_item[key] is None:
-            return json_error('missing data, expected %s' % key)
+            return json_error("missing data, expected %s" % key)
 
     # check format
-    if not len(request_item['user_hash']) == 40:
-        return json_error('the user_hash is invalid')
-    if not len(request_item['user_skey']) == 40:
-        return json_error('the user_skey is invalid')
+    if not len(request_item["user_hash"]) == 40:
+        return json_error("the user_hash is invalid")
+    if not len(request_item["user_skey"]) == 40:
+        return json_error("the user_skey is invalid")
 
     # get user
-    user = db.session.query(User).filter(User.user_hash == request_item['user_hash']).first()
+    user = (
+        db.session.query(User)
+        .filter(User.user_hash == request_item["user_hash"])
+        .first()
+    )
     if not user:
-        user = User(request_item['user_hash'])
+        user = User(request_item["user_hash"])
         db.session.add(user)
     else:
 
         # user is naughty
         if user.is_banned:
-            return json_error('account has been disabled due to abuse')
+            return json_error("account has been disabled due to abuse")
 
         # the user is too harsh
         if val < 0 and user.karma < -50:
-            return json_error('all negative karma used up')
-
-    if request_item['user_skey'] != _get_user_key(request_item['user_hash'], request_item['app_id']):
-        _eventlog_add(_get_client_address(), user.user_id, None,
-                      'invalid user_skey of %s' % request_item['user_skey'], important=True)
-        #print('expected user_skey of %s' % _get_user_key(request_item['user_hash'], request_item['app_id']))
-        return json_error('invalid user_skey')
+            return json_error("all negative karma used up")
+
+    if request_item["user_skey"] != _get_user_key(
+        request_item["user_hash"], request_item["app_id"]
+    ):
+        _eventlog_add(
+            _get_client_address(),
+            user.user_id,
+            None,
+            "invalid user_skey of %s" % request_item["user_skey"],
+            important=True,
+        )
+        # print('expected user_skey of %s' % _get_user_key(request_item['user_hash'], 
request_item['app_id']))
+        return json_error("invalid user_skey")
 
     # the user already has a review
-    if _vote_exists(request_item['review_id'], user.user_id):
-        _eventlog_add(_get_client_address(), user.user_id, request_item['app_id'],
-                      'duplicate vote')
-        return json_error('already voted on this app')
+    if _vote_exists(request_item["review_id"], user.user_id):
+        _eventlog_add(
+            _get_client_address(),
+            user.user_id,
+            request_item["app_id"],
+            "duplicate vote",
+        )
+        return json_error("already voted on this app")
 
     # update the per-user karma
     user.karma += val
 
-    review = db.session.query(Review).\
-                join(Component).\
-                filter(Component.app_id == request_item['app_id']).first()
+    review = (
+        db.session.query(Review)
+        .join(Component)
+        .filter(Component.app_id == request_item["app_id"])
+        .first()
+    )
     if not review:
-        _eventlog_add(_get_client_address(), user.user_id, None,
-                      'invalid review ID of %s' % request_item['app_id'], important=True)
-        return json_error('invalid review ID')
+        _eventlog_add(
+            _get_client_address(),
+            user.user_id,
+            None,
+            "invalid review ID of %s" % request_item["app_id"],
+            important=True,
+        )
+        return json_error("invalid review ID")
 
     # update review
     if val == -5:
@@ -390,14 +467,19 @@ def _vote(val):
     db.session.commit()
 
     # add the vote to the database
-    db.session.add(Vote(user.user_id, val, review_id=request_item['review_id']))
+    db.session.add(Vote(user.user_id, val, review_id=request_item["review_id"]))
     db.session.commit()
-    _eventlog_add(_get_client_address(), user.user_id, request_item['app_id'],
-                  'voted %i on review' % val)
+    _eventlog_add(
+        _get_client_address(),
+        user.user_id,
+        request_item["app_id"],
+        "voted %i on review" % val,
+    )
+
+    return json_success("voted #%i %i" % (request_item["review_id"], val))
 
-    return json_success('voted #%i %i' % (request_item['review_id'], val))
 
-@app.route('/1.0/reviews/api/upvote', methods=['POST'])
+@app.route("/1.0/reviews/api/upvote", methods=["POST"])
 @csrf.exempt
 def api_upvote():
     """
@@ -405,7 +487,8 @@ def api_upvote():
     """
     return _vote(1)
 
-@app.route('/1.0/reviews/api/downvote', methods=['POST'])
+
+@app.route("/1.0/reviews/api/downvote", methods=["POST"])
 @csrf.exempt
 def api_downvote():
     """
@@ -413,7 +496,8 @@ def api_downvote():
     """
     return _vote(-1)
 
-@app.route('/1.0/reviews/api/dismiss', methods=['POST'])
+
+@app.route("/1.0/reviews/api/dismiss", methods=["POST"])
 @csrf.exempt
 def api_dismiss():
     """
@@ -421,7 +505,8 @@ def api_dismiss():
     """
     return _vote(0)
 
-@app.route('/1.0/reviews/api/report', methods=['POST'])
+
+@app.route("/1.0/reviews/api/report", methods=["POST"])
 @csrf.exempt
 def api_report():
     """
@@ -429,54 +514,69 @@ def api_report():
     """
     return _vote(-5)
 
-@app.route('/1.0/reviews/api/remove', methods=['POST'])
+
+@app.route("/1.0/reviews/api/remove", methods=["POST"])
 @csrf.exempt
 def api_remove():
     """
     Remove a review.
     """
     try:
-        request_item = json.loads(request.data.decode('utf8'))
+        request_item = json.loads(request.data.decode("utf8"))
     except ValueError as e:
         return json_error(str(e))
-    for key in ['review_id', 'app_id', 'user_hash', 'user_skey']:
+    for key in ["review_id", "app_id", "user_hash", "user_skey"]:
         if not key in request_item:
-            return json_error('invalid data, required %s' % key)
+            return json_error("invalid data, required %s" % key)
         if not request_item[key]:
-            return json_error('missing data, expected %s' % key)
+            return json_error("missing data, expected %s" % key)
 
     # check format
-    if not len(request_item['user_hash']) == 40:
-        return json_error('the user_hash is invalid')
-    if not len(request_item['user_skey']) == 40:
-        return json_error('the user_skey is invalid')
+    if not len(request_item["user_hash"]) == 40:
+        return json_error("the user_hash is invalid")
+    if not len(request_item["user_skey"]) == 40:
+        return json_error("the user_skey is invalid")
 
     # the user already has a review
-    user = db.session.query(User).filter(User.user_hash == request_item['user_hash']).first()
+    user = (
+        db.session.query(User)
+        .filter(User.user_hash == request_item["user_hash"])
+        .first()
+    )
     if not user:
-        return json_error('no review')
-    review = db.session.query(Review).\
-                filter(Review.review_id == request_item['review_id']).\
-                filter(Review.user_id == user.user_id).first()
+        return json_error("no review")
+    review = (
+        db.session.query(Review)
+        .filter(Review.review_id == request_item["review_id"])
+        .filter(Review.user_id == user.user_id)
+        .first()
+    )
     if not review:
-        return json_error('no review')
-    if review.component.app_id != request_item['app_id']:
-        return json_error('the app_id is invalid')
-
-    if request_item['user_skey'] != _get_user_key(request_item['user_hash'], request_item['app_id']):
-        _eventlog_add(_get_client_address(), user.user_id, None,
-                      'invalid user_skey of %s' % request_item['user_skey'], important=True)
-        return json_error('invalid user_skey')
+        return json_error("no review")
+    if review.component.app_id != request_item["app_id"]:
+        return json_error("the app_id is invalid")
+
+    if request_item["user_skey"] != _get_user_key(
+        request_item["user_hash"], request_item["app_id"]
+    ):
+        _eventlog_add(
+            _get_client_address(),
+            user.user_id,
+            None,
+            "invalid user_skey of %s" % request_item["user_skey"],
+            important=True,
+        )
+        return json_error("invalid user_skey")
 
     db.session.delete(review)
     db.session.commit()
-    _eventlog_add(_get_client_address(),
-                  user.user_id,
-                  request_item['app_id'],
-                  'removed review')
-    return json_success('removed review #%i' % request_item['review_id'])
+    _eventlog_add(
+        _get_client_address(), user.user_id, request_item["app_id"], "removed review"
+    )
+    return json_success("removed review #%i" % request_item["review_id"])
 
-@app.route('/1.0/reviews/api/ratings/<app_id>')
+
+@app.route("/1.0/reviews/api/ratings/<app_id>")
 def api_rating_for_id(app_id):
     """
     Get the star ratings for a specific application.
@@ -485,28 +585,26 @@ def api_rating_for_id(app_id):
     component = db.session.query(Component).filter(Component.app_id == app_id).first()
     if component:
         ratings = _get_rating_for_component(component)
-    dat = json.dumps(ratings, ensure_ascii=False, sort_keys=True, indent=4, separators=(',', ': '))
-    return Response(response=dat,
-                    status=200, \
-                    mimetype='application/json')
+    dat = json.dumps(
+        ratings, ensure_ascii=False, sort_keys=True, indent=4, separators=(",", ": ")
+    )
+    return Response(response=dat, status=200, mimetype="application/json")
+
 
-@app.route('/1.0/reviews/api/ratings')
+@app.route("/1.0/reviews/api/ratings")
 def api_ratings():
     """
     Get the star ratings for all known applications.
     """
     item = {}
-    for component in db.session.query(Component).\
-                                      order_by(Component.app_id.asc()).all():
+    for component in db.session.query(Component).order_by(Component.app_id.asc()).all():
         ratings = _get_rating_for_component(component, 2)
         if len(ratings) == 0:
             continue
         item[component.app_id] = ratings
 
-    dat = json.dumps(item, sort_keys=True, indent=4, separators=(',', ': '))
-    response = Response(response=dat,
-                        status=200, \
-                        mimetype='application/json')
+    dat = json.dumps(item, sort_keys=True, indent=4, separators=(",", ": "))
+    response = Response(response=dat, status=200, mimetype="application/json")
     response.cache_control.public = True
     response.add_etag()
     return response.make_conditional(request)


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]