[damned-lies/develop] refactor: apply black and isort on all files



commit 8a6fe6783cadac151b831600761e2d93a1c832ef
Author: Guillaume Bernard <associations guillaume-bernard fr>
Date:   Mon Sep 5 17:07:01 2022 +0200

    refactor: apply black and isort on all files

 api/tests.py                                       |  203 +--
 api/urls.py                                        |   42 +-
 api/views.py                                       |  224 ++--
 common/backends.py                                 |    4 +-
 common/context_processors.py                       |    2 +-
 common/fields.py                                   |    5 +-
 common/middleware.py                               |    4 +-
 common/templatetags/list_to_columns.py             |    4 +-
 common/tests.py                                    |  107 +-
 common/utils.py                                    |   61 +-
 common/views.py                                    |   95 +-
 damnedlies/context_processors.py                   |    1 -
 damnedlies/settings.py                             |  200 ++-
 damnedlies/settings_tests.py                       |    8 +-
 damnedlies/urls.py                                 |  156 +--
 docs/source/conf.py                                |   46 +-
 feeds/urls.py                                      |    6 +-
 languages/admin.py                                 |    3 +-
 languages/management/commands/load-plurals.py      |  218 ++--
 languages/migrations/0001_initial.py               |   23 +-
 languages/models.py                                |   35 +-
 languages/tests.py                                 |   24 +-
 languages/urls.py                                  |   32 +-
 languages/views.py                                 |  139 +-
 people/admin.py                                    |    7 +-
 people/forms.py                                    |  107 +-
 people/migrations/0001_initial.py                  |   62 +-
 .../0002_set_use_gravatar_verbose_name.py          |   10 +-
 people/migrations/0003_person_avatar_service.py    |   13 +-
 people/migrations/0004_migrate_use_gravatar.py     |    5 +-
 .../migrations/0005_remove_person_use_gravatar.py  |    6 +-
 .../0006_remove_person_bugzilla_account.py         |    6 +-
 people/migrations/0007_person_auth_token.py        |    8 +-
 people/models.py                                   |   83 +-
 people/templatetags/people.py                      |   15 +-
 people/tests.py                                    |  227 ++--
 people/urls.py                                     |   36 +-
 people/views.py                                    |   84 +-
 pyproject.toml                                     |    8 +-
 setup.py                                           |    5 +-
 stats/admin.py                                     |  151 ++-
 stats/doap.py                                      |   40 +-
 stats/forms.py                                     |   55 +-
 stats/management/commands/archive-release.py       |   28 +-
 stats/management/commands/compile-trans.py         |   25 +-
 stats/management/commands/run-maintenance.py       |    2 +-
 stats/management/commands/update-from-doap.py      |    7 +-
 stats/management/commands/update-stats.py          |   68 +-
 stats/management/commands/update-trans.py          |    7 +-
 stats/migrations/0001_initial.py                   |  368 ++++--
 stats/migrations/0002_add_category_name.py         |   20 +-
 stats/migrations/0003_migrate_category_names.py    |   40 +-
 stats/migrations/0004_remove_old_cat_name.py       |   20 +-
 stats/migrations/0005_update_module_name_field.py  |   21 +-
 stats/migrations/0006_add_domain_branch_from_to.py |   31 +-
 stats/migrations/0007_extend_bugs_base.py          |    6 +-
 stats/migrations/0008_domain_extra_its_dirs.py     |   10 +-
 .../migrations/0009_remove_null_on_text_fields.py  |   96 +-
 stats/migrations/0010_pot_method.py                |   36 +-
 stats/migrations/0011_migrate_pot_method.py        |   22 +-
 .../0012_remove_domain_pot_method_old.py           |    6 +-
 stats/migrations/0013_domain_layout.py             |   12 +-
 stats/migrations/0014_migrate_dir_to_layout.py     |   10 +-
 stats/migrations/0015_remove_domain_directory.py   |    6 +-
 stats/migrations/0016_removed_bugs_fields.py       |   10 +-
 stats/migrations/0017_pofile_path_relative.py      |   14 +-
 stats/migrations/0018_new_jsonfields.py            |   22 +-
 stats/migrations/0019_migrate_old_custom_files.py  |    4 +-
 stats/migrations/0020_remove_pofile_figures_old.py |   10 +-
 stats/migrations/0021_release_name_unique.py       |    6 +-
 stats/models.py                                    | 1374 ++++++++++----------
 stats/potdiff.py                                   |    9 +-
 stats/repos.py                                     |   78 +-
 stats/templatetags/stats_extras.py                 |  152 ++-
 stats/tests/fixture_factory.py                     |  143 +-
 stats/tests/tests.py                               |  845 ++++++------
 stats/tests/utils.py                               |   20 +-
 stats/utils.py                                     |  345 +++--
 stats/views.py                                     |  225 ++--
 teams/admin.py                                     |   18 +-
 teams/forms.py                                     |   64 +-
 teams/migrations/0001_initial.py                   |   73 +-
 teams/models.py                                    |   93 +-
 teams/tests.py                                     |  175 ++-
 teams/urls.py                                      |   13 +-
 teams/views.py                                     |  108 +-
 vertimus/admin.py                                  |   18 +-
 vertimus/feeds.py                                  |   71 +-
 vertimus/forms.py                                  |   79 +-
 vertimus/migrations/0001_initial.py                |  278 ++--
 vertimus/migrations/0002_state_person_blank.py     |    8 +-
 vertimus/migrations/0003_add_action_sent_to_ml.py  |   10 +-
 vertimus/migrations/0004_tables_to_utf8mb4.py      |    7 +-
 vertimus/migrations/0005_action_proxy_pofile.py    |   31 +-
 vertimus/migrations/0006_pofile_path_relative.py   |    5 +-
 .../migrations/0008_actions_on_delete_setnull.py   |   16 +-
 vertimus/models.py                                 |  362 +++---
 vertimus/templatetags/vertimus.py                  |   32 +-
 vertimus/tests/tests.py                            |  627 ++++-----
 vertimus/urls.py                                   |   53 +-
 vertimus/views.py                                  |  322 ++---
 101 files changed, 4667 insertions(+), 4464 deletions(-)
---
diff --git a/api/tests.py b/api/tests.py
index 482497ab..0ef7abf8 100644
--- a/api/tests.py
+++ b/api/tests.py
@@ -14,189 +14,194 @@ from vertimus.views import get_vertimus_state
 
 
 class APITests(TestCase):
-    fixtures = ['sample_data.json']
+    fixtures = ["sample_data.json"]
 
     def test_home(self):
-        response = self.client.get(reverse('api-home'))
-        self.assertEqual(response.json(), {
-            'languages': '/api/v1/languages/',
-            'modules': '/api/v1/modules/',
-            'releases': '/api/v1/releases/',
-            'teams': '/api/v1/teams/',
-        })
+        response = self.client.get(reverse("api-home"))
+        self.assertEqual(
+            response.json(),
+            {
+                "languages": "/api/v1/languages/",
+                "modules": "/api/v1/modules/",
+                "releases": "/api/v1/releases/",
+                "teams": "/api/v1/teams/",
+            },
+        )
 
     def test_modules(self):
-        response = self.client.get(reverse('api-modules'))
+        response = self.client.get(reverse("api-modules"))
         result = response.json()
-        self.assertEqual(result[0], {'name': 'gnome-hello', 'href': '/api/v1/modules/gnome-hello'})
+        self.assertEqual(result[0], {"name": "gnome-hello", "href": "/api/v1/modules/gnome-hello"})
 
     def test_module(self):
-        response = self.client.get(reverse('api-module', args=['gnome-hello']))
+        response = self.client.get(reverse("api-module", args=["gnome-hello"]))
         result = response.json()
-        self.assertEqual(result['vcs_web'], 'https://gitlab.gnome.org/GNOME/gnome-hello/')
-        self.assertEqual(result['branches'], [{'name': 'master'}])
+        self.assertEqual(result["vcs_web"], "https://gitlab.gnome.org/GNOME/gnome-hello/";)
+        self.assertEqual(result["branches"], [{"name": "master"}])
         self.assertEqual(
-            result['domains'][1],
-            {'description': 'UI Translations', 'name': 'po', 'dtype': 'ui', 'layout': 'po/{lang}.po'}
+            result["domains"][1],
+            {"description": "UI Translations", "name": "po", "dtype": "ui", "layout": "po/{lang}.po"},
         )
 
     def test_module_branch(self):
-        response = self.client.get(reverse('api-module-branch', args=['gnome-hello', 'master']))
+        response = self.client.get(reverse("api-module-branch", args=["gnome-hello", "master"]))
         result = response.json()
         self.assertEqual(
-            result, {
-                'module': 'gnome-hello',
-                'branch': 'master',
-                'domains': [{
-                    'name': 'po',
-                    'statistics': {
-                        'fr': {'fuzzy': 0, 'trans': 47, 'untrans': 0},
-                        'it': {'fuzzy': 10, 'trans': 30, 'untrans': 7}
-                    }}, {
-                    'name': 'help',
-                    'statistics': {
-                        'fr': {'fuzzy': 0, 'trans': 20, 'untrans': 0},
-                        'it': {'fuzzy': 0, 'trans': 20, 'untrans': 0}
-                    }}
+            result,
+            {
+                "module": "gnome-hello",
+                "branch": "master",
+                "domains": [
+                    {
+                        "name": "po",
+                        "statistics": {
+                            "fr": {"fuzzy": 0, "trans": 47, "untrans": 0},
+                            "it": {"fuzzy": 10, "trans": 30, "untrans": 7},
+                        },
+                    },
+                    {
+                        "name": "help",
+                        "statistics": {
+                            "fr": {"fuzzy": 0, "trans": 20, "untrans": 0},
+                            "it": {"fuzzy": 0, "trans": 20, "untrans": 0},
+                        },
+                    },
                 ],
-            }
+            },
         )
 
     def test_teams(self):
-        response = self.client.get(reverse('api-teams'))
+        response = self.client.get(reverse("api-teams"))
         result = response.json()
-        self.assertEqual(result[0], {'name': 'fr', 'href': '/api/v1/teams/fr', 'description': 'French'})
+        self.assertEqual(result[0], {"name": "fr", "href": "/api/v1/teams/fr", "description": "French"})
 
     def test_team(self):
-        response = self.client.get(reverse('api-team', args=['fr']))
+        response = self.client.get(reverse("api-team", args=["fr"]))
         result = response.json()
-        self.assertEqual(result['coordinators'], [{'name': 'John Coordinator'}])
+        self.assertEqual(result["coordinators"], [{"name": "John Coordinator"}])
 
     def test_languages(self):
-        response = self.client.get(reverse('api-languages'))
+        response = self.client.get(reverse("api-languages"))
         result = response.json()
-        self.assertEqual(result[0], {
-            'href': '/api/v1/teams/bem',
-            'locale': 'bem',
-            'name': 'Bemba',
-            'plurals': '',
-            'team__description': None
-        })
+        self.assertEqual(
+            result[0],
+            {"href": "/api/v1/teams/bem", "locale": "bem", "name": "Bemba", "plurals": "", 
"team__description": None},
+        )
 
     def test_releases(self):
-        response = self.client.get(reverse('api-releases'))
+        response = self.client.get(reverse("api-releases"))
         result = response.json()
-        self.assertEqual(result[0], {
-            'description': 'freedesktop.org (non-GNOME)',
-            'href': '/api/v1/releases/freedesktop-org',
-            'name': 'freedesktop-org'
-        })
+        self.assertEqual(
+            result[0],
+            {
+                "description": "freedesktop.org (non-GNOME)",
+                "href": "/api/v1/releases/freedesktop-org",
+                "name": "freedesktop-org",
+            },
+        )
 
     def test_release(self):
-        response = self.client.get(reverse('api-release', args=['gnome-3-8']))
+        response = self.client.get(reverse("api-release", args=["gnome-3-8"]))
         result = response.json()
-        self.assertEqual(result['description'], 'GNOME 3.8 (stable)')
-        self.assertEqual(
-            result['languages'][0],
-            {'href': '/api/v1/releases/gnome-3-8/languages/bem', 'locale': 'bem'}
-        )
-        self.assertEqual(result['branches'][0], 'gnome-3-8 (zenity)')
-        self.assertEqual(result['statistics'], '/api/v1/releases/gnome-3-8/stats')
+        self.assertEqual(result["description"], "GNOME 3.8 (stable)")
+        self.assertEqual(result["languages"][0], {"href": "/api/v1/releases/gnome-3-8/languages/bem", 
"locale": "bem"})
+        self.assertEqual(result["branches"][0], "gnome-3-8 (zenity)")
+        self.assertEqual(result["statistics"], "/api/v1/releases/gnome-3-8/stats")
 
     def test_release_stats(self):
-        response = self.client.get(reverse('api-release-stats', args=['gnome-3-8']))
+        response = self.client.get(reverse("api-release-stats", args=["gnome-3-8"]))
         result = response.json()
-        self.assertEqual(result['statistics'][0]['lang_name'], 'French')
+        self.assertEqual(result["statistics"][0]["lang_name"], "French")
         self.assertEqual(
-            result['statistics'][0]['ui'],
-            {'translated_perc': 100, 'untranslated_perc': 0, 'translated': 183,
-             'fuzzy_perc': 0, 'untranslated': 0, 'fuzzy': 0}
+            result["statistics"][0]["ui"],
+            {
+                "translated_perc": 100,
+                "untranslated_perc": 0,
+                "translated": 183,
+                "fuzzy_perc": 0,
+                "untranslated": 0,
+                "fuzzy": 0,
+            },
         )
 
     def test_release_language(self):
-        response = self.client.get(reverse('api-release-language', args=['gnome-3-8', 'fr']))
+        response = self.client.get(reverse("api-release-language", args=["gnome-3-8", "fr"]))
         result = response.json()
-        self.assertEqual(len(result['modules']), 4)
+        self.assertEqual(len(result["modules"]), 4)
 
     def test_module_lang_stats(self):
-        response = self.client.get(
-            reverse('api-module-lang-stats', args=['gnome-hello', 'master', 'po', 'fr'])
-        )
+        response = self.client.get(reverse("api-module-lang-stats", args=["gnome-hello", "master", "po", 
"fr"]))
         result = response.json()
-        self.assertEqual(result['statistics'], {'untrans': 0, 'fuzzy': 0, 'trans': 47})
-        self.assertEqual(result['pot_file'], '/POT/gnome-hello.master/gnome-hello.master.pot')
-        self.assertEqual(result['po_file'], '/POT/gnome-hello.master/gnome-hello.master.fr.po')
+        self.assertEqual(result["statistics"], {"untrans": 0, "fuzzy": 0, "trans": 47})
+        self.assertEqual(result["pot_file"], "/POT/gnome-hello.master/gnome-hello.master.pot")
+        self.assertEqual(result["po_file"], "/POT/gnome-hello.master/gnome-hello.master.fr.po")
 
     def test_reserve_translation(self):
         translator = Person.objects.create(
-            first_name='John', last_name='Translator',
-            email='jt devnull com', username='translator'
+            first_name="John", last_name="Translator", email="jt devnull com", username="translator"
         )
-        team = Team.objects.get(name='fr')
+        team = Team.objects.get(name="fr")
         Role.objects.create(team=team, person=translator)
-        url = reverse('api-reserve', args=['gnome-hello', 'master', 'po', 'fr'])
+        url = reverse("api-reserve", args=["gnome-hello", "master", "po", "fr"])
         # Test anonymous cannot post
         response = self.client.post(url, data={})
-        self.assertRedirects(response, reverse('login') + f'?next={url}')
+        self.assertRedirects(response, reverse("login") + f"?next={url}")
 
         self.client.force_login(translator)
         response = self.client.post(url, data={})
-        self.assertEqual(response.json(), {'result': 'OK'})
+        self.assertEqual(response.json(), {"result": "OK"})
         self.assertEqual(len(mail.outbox), 1)
         self.assertEqual(mail.outbox[0].recipients(), [team.mailing_list])
         state = State.objects.get(person=translator)
-        self.assertEqual(state.name, 'Translating')
+        self.assertEqual(state.name, "Translating")
         # Test cannot reserve a second time
         response = self.client.post(url, data={})
         self.assertEqual(response.status_code, 403)
 
     def test_upload_translation(self):
         translator = Person.objects.create(
-            first_name='John', last_name='Translator',
-            email='jt devnull com', username='translator'
-        )
-        somebody = Person.objects.create(
-            email='some devnull com', username='somebody'
+            first_name="John", last_name="Translator", email="jt devnull com", username="translator"
         )
-        team = Team.objects.get(name='fr')
+        somebody = Person.objects.create(email="some devnull com", username="somebody")
+        team = Team.objects.get(name="fr")
         Role.objects.create(team=team, person=translator)
         Role.objects.create(team=team, person=somebody)
 
         _, _, state = get_vertimus_state(
-            Branch.objects.get(module__name='gnome-hello'),
-            Domain.objects.get(module__name='gnome-hello', name='po'),
-            Language.objects.get(locale='fr')
+            Branch.objects.get(module__name="gnome-hello"),
+            Domain.objects.get(module__name="gnome-hello", name="po"),
+            Language.objects.get(locale="fr"),
         )
-        url = reverse('api-upload', args=['gnome-hello', 'master', 'po', 'fr'])
+        url = reverse("api-upload", args=["gnome-hello", "master", "po", "fr"])
         test_po = Path(__file__).parent.parent / "stats" / "tests" / "test.po"
         # Test anonymous cannot post
-        with test_po.open('rb') as fh:
-            response = self.client.post(url, data={'file': File(fh)})
-            self.assertRedirects(response, reverse('login') + f'?next={url}')
+        with test_po.open("rb") as fh:
+            response = self.client.post(url, data={"file": File(fh)})
+            self.assertRedirects(response, reverse("login") + f"?next={url}")
 
         state.change_state(StateTranslating, person=translator)
 
         # somebody cannot post translation if reserved by translator.
         self.client.force_login(somebody)
-        with test_po.open('rb') as fh:
-            response = self.client.post(url, data={'file': File(fh)})
+        with test_po.open("rb") as fh:
+            response = self.client.post(url, data={"file": File(fh)})
             self.assertEqual(response.status_code, 403)
 
         self.client.force_login(translator)
-        with test_po.open('rb') as fh:
-            response = self.client.post(url, data={'file': File(fh)})
-        self.assertEqual(response.json(), {'result': 'OK'})
+        with test_po.open("rb") as fh:
+            response = self.client.post(url, data={"file": File(fh)})
+        self.assertEqual(response.json(), {"result": "OK"})
         self.assertEqual(len(mail.outbox), 1)
         self.assertEqual(mail.outbox[0].recipients(), [team.mailing_list])
 
         # Test upload with comment
         state.change_state(StateTranslating, person=translator)
-        with test_po.open('rb') as fh:
+        with test_po.open("rb") as fh:
             data = {
-                'file': File(fh),
-                'comment': 'The comment',
+                "file": File(fh),
+                "comment": "The comment",
             }
             response = self.client.post(url, data=data)
-        self.assertEqual(response.json(), {'result': 'OK'})
+        self.assertEqual(response.json(), {"result": "OK"})
         action = Action.objects.last()
         self.assertEqual(action.comment, "The comment")
diff --git a/api/urls.py b/api/urls.py
index e96f2a48..f1821f51 100644
--- a/api/urls.py
+++ b/api/urls.py
@@ -3,40 +3,36 @@ from django.urls import path
 from . import views
 
 complete_translatable_path = (
-    'modules/<name:module_name>/branches/<path:branch_name>/domains/<name:domain_name>'
-    '/languages/<locale:lang>'
+    "modules/<name:module_name>/branches/<path:branch_name>/domains/<name:domain_name>" 
"/languages/<locale:lang>"
 )
 
 urlpatterns = [
-    path('', views.APIHomeView.as_view(), name='api-home'),
-    path('modules/', views.ModulesView.as_view(), name='api-modules'),
-    path('modules/<name:module_name>', views.ModuleView.as_view(), name='api-module'),
+    path("", views.APIHomeView.as_view(), name="api-home"),
+    path("modules/", views.ModulesView.as_view(), name="api-modules"),
+    path("modules/<name:module_name>", views.ModuleView.as_view(), name="api-module"),
     path(
-        'modules/<name:module_name>/branch/<path:branch_name>',
+        "modules/<name:module_name>/branch/<path:branch_name>",
         views.ModuleBranchView.as_view(),
-        name='api-module-branch',
+        name="api-module-branch",
     ),
-    path('teams/', views.TeamsView.as_view(), name='api-teams'),
-    path('teams/<locale:team_name>', views.TeamView.as_view(), name='api-team'),
-    path('languages/', views.LanguagesView.as_view(), name='api-languages'),
-    path('releases/', views.ReleasesView.as_view(), name='api-releases'),
-    path('releases/<name:release>', views.ReleaseView.as_view(), name='api-release'),
-    path('releases/<name:release>/stats', views.ReleaseStatsView.as_view(), name='api-release-stats'),
+    path("teams/", views.TeamsView.as_view(), name="api-teams"),
+    path("teams/<locale:team_name>", views.TeamView.as_view(), name="api-team"),
+    path("languages/", views.LanguagesView.as_view(), name="api-languages"),
+    path("releases/", views.ReleasesView.as_view(), name="api-releases"),
+    path("releases/<name:release>", views.ReleaseView.as_view(), name="api-release"),
+    path("releases/<name:release>/stats", views.ReleaseStatsView.as_view(), name="api-release-stats"),
     path(
-        'releases/<name:release>/languages/<locale:lang>', views.ReleaseLanguageView.as_view(),
-        name='api-release-language'
-    ),
-    path(
-        complete_translatable_path, views.ModuleLangStatsView.as_view(), name='api-module-lang-stats'
+        "releases/<name:release>/languages/<locale:lang>",
+        views.ReleaseLanguageView.as_view(),
+        name="api-release-language",
     ),
+    path(complete_translatable_path, views.ModuleLangStatsView.as_view(), name="api-module-lang-stats"),
     # APIs to reserve translation and upload translation, as could be used by an external
     # translation tool.
-    path(f'{complete_translatable_path}/reserve', views.ReserveTranslationView.as_view(), 
name='api-reserve'),
-    path(f'{complete_translatable_path}/upload', views.UploadTranslationView.as_view(), name='api-upload'),
-
+    path(f"{complete_translatable_path}/reserve", views.ReserveTranslationView.as_view(), 
name="api-reserve"),
+    path(f"{complete_translatable_path}/upload", views.UploadTranslationView.as_view(), name="api-upload"),
     # Used by a GitLab webhook to signal a commit for that module/branch
     path(
-        'modules/<name:module_name>/branches/<path:branch_name>/ping', views.rebuild_branch,
-        name='api-module-rebuild'
+        "modules/<name:module_name>/branches/<path:branch_name>/ping", views.rebuild_branch, 
name="api-module-rebuild"
     ),
 ]
diff --git a/api/views.py b/api/views.py
index 3e9a8943..9988ac09 100644
--- a/api/views.py
+++ b/api/views.py
@@ -20,13 +20,11 @@ from vertimus.views import get_vertimus_state
 
 class SerializeListView(View):
     def get(self, request, *args, **kwargs):
-        return JsonResponse(
-            self.serialize_qs(self.get_queryset().values(*['pk'] + self.fields)), safe=False
-        )
+        return JsonResponse(self.serialize_qs(self.get_queryset().values(*["pk"] + self.fields)), safe=False)
 
     def serialize_obj(self, obj, fields=None):
         data = {field: obj[field] for field in fields}
-        data['href'] = self.get_href(obj)
+        data["href"] = self.get_href(obj)
         return data
 
     def serialize_qs(self, qs):
@@ -43,44 +41,40 @@ class SerializeObjectView(SerializeListView):
 
 class APIHomeView(View):
     def get(self, *args, **kwargs):
-        return JsonResponse({
-            'modules': reverse('api-modules'),
-            'teams': reverse('api-teams'),
-            'languages': reverse('api-languages'),
-            'releases': reverse('api-releases'),
-        })
+        return JsonResponse(
+            {
+                "modules": reverse("api-modules"),
+                "teams": reverse("api-teams"),
+                "languages": reverse("api-languages"),
+                "releases": reverse("api-releases"),
+            }
+        )
 
 
 class ModulesView(SerializeListView):
-    fields = ['name']
+    fields = ["name"]
 
     def get_queryset(self):
         return Module.objects.filter(archived=False)
 
     def get_href(self, obj):
-        return reverse('api-module', args=[obj['name']])
+        return reverse("api-module", args=[obj["name"]])
 
 
 class ModuleView(SerializeObjectView):
     @property
     def fields(self):
-        return [
-            f.name for f in Module._meta.get_fields()
-            if not f.one_to_many and f.name != 'id'
-        ]
+        return [f.name for f in Module._meta.get_fields() if not f.one_to_many and f.name != "id"]
 
     def get_object(self):
-        return get_object_or_404(Module, name=self.kwargs['module_name'])
+        return get_object_or_404(Module, name=self.kwargs["module_name"])
 
     def serialize_obj(self, obj, **kwargs):
         data = serialize_instance(obj, self.fields)
-        data['branches'] = [
-            serialize_instance(branch, ['name'])
-            for branch in obj.get_branches()
-        ]
-        data['domains'] = [
-            serialize_instance(domain, ['name', 'description', 'dtype', 'layout'])
-            for domain in obj.domain_set.all().order_by('name')
+        data["branches"] = [serialize_instance(branch, ["name"]) for branch in obj.get_branches()]
+        data["domains"] = [
+            serialize_instance(domain, ["name", "description", "dtype", "layout"])
+            for domain in obj.domain_set.all().order_by("name")
         ]
         return data
 
@@ -88,181 +82,185 @@ class ModuleView(SerializeObjectView):
 class ModuleBranchView(SerializeObjectView):
     def get_object(self):
         return get_object_or_404(
-            Branch.objects.select_related('module'),
-            module__name=self.kwargs['module_name'], name=self.kwargs['branch_name']
+            Branch.objects.select_related("module"),
+            module__name=self.kwargs["module_name"],
+            name=self.kwargs["branch_name"],
         )
 
     def serialize_obj(self, branch, **kwargs):
         data = {
-            'module': branch.module.name,
-            'branch': branch.name,
-            'domains': [],
+            "module": branch.module.name,
+            "branch": branch.name,
+            "domains": [],
         }
         for dom_name in branch.get_domains().keys():
             stats = {}
             for stat in Statistics.objects.filter(branch=branch, domain__name=dom_name, 
language__isnull=False):
                 stats[stat.language.locale] = {
-                    'trans': stat.translated(),
-                    'fuzzy': stat.fuzzy(),
-                    'untrans': stat.untranslated(),
+                    "trans": stat.translated(),
+                    "fuzzy": stat.fuzzy(),
+                    "untrans": stat.untranslated(),
                 }
-            data['domains'].append({'name': dom_name, 'statistics': stats})
+            data["domains"].append({"name": dom_name, "statistics": stats})
         return data
 
 
 class TeamsView(SerializeListView):
-    fields = ['name', 'description']
+    fields = ["name", "description"]
 
     def get_queryset(self):
-        return Team.objects.all().order_by('name')
+        return Team.objects.all().order_by("name")
 
     def get_href(self, obj):
-        return reverse('api-team', args=[obj['name']])
+        return reverse("api-team", args=[obj["name"]])
 
 
 class TeamView(SerializeObjectView):
     @property
     def fields(self):
-        return [
-            f.name for f in Team._meta.get_fields()
-            if not f.one_to_many and f.name not in ['id', 'members']
-        ]
+        return [f.name for f in Team._meta.get_fields() if not f.one_to_many and f.name not in ["id", 
"members"]]
 
     def get_object(self):
-        return get_object_or_404(Team, name=self.kwargs['team_name'])
+        return get_object_or_404(Team, name=self.kwargs["team_name"])
 
     def serialize_obj(self, obj, **kwargs):
         data = serialize_instance(obj, self.fields)
-        data['coordinators'] = [
-            serialize_instance(coord, ['name'])
-            for coord in obj.get_coordinators()
-        ]
+        data["coordinators"] = [serialize_instance(coord, ["name"]) for coord in obj.get_coordinators()]
         return data
 
 
 class LanguagesView(SerializeListView):
-    fields = ['name', 'locale', 'team__description', 'plurals']
+    fields = ["name", "locale", "team__description", "plurals"]
 
     def get_queryset(self):
-        return Language.objects.all().order_by('name')
+        return Language.objects.all().order_by("name")
 
     def get_href(self, obj):
-        return reverse('api-team', args=[obj['locale']])
+        return reverse("api-team", args=[obj["locale"]])
 
 
 class ReleasesView(SerializeListView):
-    fields = ['name', 'description']
+    fields = ["name", "description"]
 
     def get_queryset(self):
-        return Release.objects.all().order_by('name')
+        return Release.objects.all().order_by("name")
 
     def get_href(self, obj):
-        return reverse('api-release', args=[obj['name']])
+        return reverse("api-release", args=[obj["name"]])
 
 
 class ReleaseView(SerializeObjectView):
-    fields = ['name', 'description', 'string_frozen', 'status', 'branches']
+    fields = ["name", "description", "string_frozen", "status", "branches"]
 
     def get_object(self):
-        return get_object_or_404(Release, name=self.kwargs['release'])
+        return get_object_or_404(Release, name=self.kwargs["release"])
 
     def serialize_obj(self, obj, **kwargs):
         data = serialize_instance(obj, self.fields)
-        data['languages'] = [{
-            'locale': lang.locale,
-            'href': reverse('api-release-language', args=[obj.name, lang.locale])
-        } for lang in Language.objects.all()]
-        data['statistics'] = reverse('api-release-stats', args=[self.kwargs['release']])
+        data["languages"] = [
+            {"locale": lang.locale, "href": reverse("api-release-language", args=[obj.name, lang.locale])}
+            for lang in Language.objects.all()
+        ]
+        data["statistics"] = reverse("api-release-stats", args=[self.kwargs["release"]])
         return data
 
 
 class ReleaseStatsView(View):
     def get(self, *args, **kwargs):
-        release = get_object_or_404(Release, name=self.kwargs['release'])
+        release = get_object_or_404(Release, name=self.kwargs["release"])
         lang_stats = release.get_global_stats()
-        return JsonResponse({
-            'release': serialize_instance(release, ['name', 'description', 'status']),
-            'statistics': lang_stats,
-        })
+        return JsonResponse(
+            {
+                "release": serialize_instance(release, ["name", "description", "status"]),
+                "statistics": lang_stats,
+            }
+        )
 
 
 class ReleaseLanguageView(View):
     def get(self, *args, **kwargs):
-        release = get_object_or_404(Release, name=self.kwargs['release'])
-        language = get_object_or_404(Language, locale=self.kwargs['lang'])
+        release = get_object_or_404(Release, name=self.kwargs["release"])
+        language = get_object_or_404(Language, locale=self.kwargs["lang"])
         item_list = []
-        for branch in release.branches.all().select_related('module'):
+        for branch in release.branches.all().select_related("module"):
             for domain in branch.get_domains():
-                item_list.append({
-                    'module': branch.module.name,
-                    'branch': branch.name,
-                    'stats': reverse('api-module-lang-stats', kwargs={
-                        'module_name': branch.module.name,
-                        'branch_name': branch.name,
-                        'domain_name': domain,
-                        'lang': language.locale,
-                    }),
-                })
-        return JsonResponse({
-            'release': release.name,
-            'language': language.locale,
-            'modules': item_list,
-        })
+                item_list.append(
+                    {
+                        "module": branch.module.name,
+                        "branch": branch.name,
+                        "stats": reverse(
+                            "api-module-lang-stats",
+                            kwargs={
+                                "module_name": branch.module.name,
+                                "branch_name": branch.name,
+                                "domain_name": domain,
+                                "lang": language.locale,
+                            },
+                        ),
+                    }
+                )
+        return JsonResponse(
+            {
+                "release": release.name,
+                "language": language.locale,
+                "modules": item_list,
+            }
+        )
 
 
 class VertimusPageMixin:
     def get_state_from_kwargs(self):
-        module = get_object_or_404(Module, name=self.kwargs['module_name'])
-        branch = get_object_or_404(module.branch_set, name=self.kwargs['branch_name'])
-        domain = branch.get_domains().get(self.kwargs['domain_name'])
+        module = get_object_or_404(Module, name=self.kwargs["module_name"])
+        branch = get_object_or_404(module.branch_set, name=self.kwargs["branch_name"])
+        domain = branch.get_domains().get(self.kwargs["domain_name"])
         if not domain:
             raise Http404
-        language = get_object_or_404(Language, locale=self.kwargs['lang'])
+        language = get_object_or_404(Language, locale=self.kwargs["lang"])
         return get_vertimus_state(branch, domain, language)[1:]
 
 
 class ModuleLangStatsView(VertimusPageMixin, View):
     def get(self, *args, **kwargs):
         stats, state = self.get_state_from_kwargs()
-        return JsonResponse({
-            'module': state.branch.module.name,
-            'branch': state.branch.name,
-            'domain': state.domain.name,
-            'language': state.language.locale,
-            'state': state.name,
-            'statistics': {
-                'trans': stats.translated(), 'fuzzy': stats.fuzzy(), 'untrans': stats.untranslated()
-            },
-            'pot_file': stats.pot_url(),
-            'po_file': stats.po_url(),
-        })
-
-
-@method_decorator(csrf_exempt, name='dispatch')
+        return JsonResponse(
+            {
+                "module": state.branch.module.name,
+                "branch": state.branch.name,
+                "domain": state.domain.name,
+                "language": state.language.locale,
+                "state": state.name,
+                "statistics": {"trans": stats.translated(), "fuzzy": stats.fuzzy(), "untrans": 
stats.untranslated()},
+                "pot_file": stats.pot_url(),
+                "po_file": stats.po_url(),
+            }
+        )
+
+
+@method_decorator(csrf_exempt, name="dispatch")
 class ReserveTranslationView(LoginRequiredMixin, VertimusPageMixin, View):
     def post(self, request, *args, **kwargs):
         stats, state = self.get_state_from_kwargs()
         actions = [x.name for x in state.get_available_actions(request.user.person)]
-        if 'RT' not in actions:
-            return HttpResponseForbidden('This module cannot be reserved')
+        if "RT" not in actions:
+            return HttpResponseForbidden("This module cannot be reserved")
 
         action = ActionRT(person=request.user.person)
-        action.apply_on(state, {'comment': '', 'send_to_ml': True})
-        return JsonResponse({'result': 'OK'})
+        action.apply_on(state, {"comment": "", "send_to_ml": True})
+        return JsonResponse({"result": "OK"})
 
 
-@method_decorator(csrf_exempt, name='dispatch')
+@method_decorator(csrf_exempt, name="dispatch")
 class UploadTranslationView(LoginRequiredMixin, VertimusPageMixin, View):
     def post(self, request, *args, **kwargs):
         stats, state = self.get_state_from_kwargs()
         actions = [x.name for x in state.get_available_actions(request.user.person)]
-        if 'UT' not in actions:
-            return HttpResponseForbidden('You cannot upload a translation to this module')
+        if "UT" not in actions:
+            return HttpResponseForbidden("You cannot upload a translation to this module")
 
-        action = ActionUT(person=request.user.person, file=request.FILES.get('file'))
-        comment = request.POST.get('comment', '')
-        action.apply_on(state, {'comment': comment, 'send_to_ml': True})
-        return JsonResponse({'result': 'OK'})
+        action = ActionUT(person=request.user.person, file=request.FILES.get("file"))
+        comment = request.POST.get("comment", "")
+        action.apply_on(state, {"comment": comment, "send_to_ml": True})
+        return JsonResponse({"result": "OK"})
 
 
 # CSRF skipped, verification using a secret token.
@@ -276,7 +274,7 @@ def rebuild_branch(request, module_name, branch_name):
     # Run effective work in a separate thread to prevent timeouts
     thread = Thread(target=rebuild_branch_real, args=(branch,))
     thread.start()
-    return JsonResponse({'result': 'OK'})
+    return JsonResponse({"result": "OK"})
 
 
 def rebuild_branch_real(branch):
@@ -291,7 +289,7 @@ def serialize_instance(instance, field_names):
     data = {}
     for field in field_names:
         value = getattr(instance, field)
-        if hasattr(value, 'all'):
+        if hasattr(value, "all"):
             value = [str(o) for o in value.all()]
         data[field] = value
     return data
diff --git a/common/backends.py b/common/backends.py
index cdf505c5..9abcc918 100644
--- a/common/backends.py
+++ b/common/backends.py
@@ -5,8 +5,8 @@ from people.models import Person
 
 class TokenBackend(ModelBackend):
     def authenticate(self, request, **kwargs):
-        auth = request.META.get('HTTP_AUTHENTICATION', '').split()
-        if len(auth) != 2 or auth[0] != 'Bearer':
+        auth = request.META.get("HTTP_AUTHENTICATION", "").split()
+        if len(auth) != 2 or auth[0] != "Bearer":
             return
         try:
             user = Person.objects.get(auth_token=auth[1])
diff --git a/common/context_processors.py b/common/context_processors.py
index db9d3fa4..c8eddf1b 100644
--- a/common/context_processors.py
+++ b/common/context_processors.py
@@ -2,4 +2,4 @@ from django.conf import settings
 
 
 def utils(request):
-    return {'bug_url': settings.ENTER_BUG_URL}
+    return {"bug_url": settings.ENTER_BUG_URL}
diff --git a/common/fields.py b/common/fields.py
index ec1fd29c..ee551d03 100644
--- a/common/fields.py
+++ b/common/fields.py
@@ -25,7 +25,7 @@ class DictionaryField(models.Field):
             try:
                 return dict(json.loads(value))
             except (ValueError, TypeError):
-                raise exceptions.ValidationError(self.error_messages['invalid'])
+                raise exceptions.ValidationError(self.error_messages["invalid"])
 
         if isinstance(value, dict):
             return value
@@ -47,13 +47,14 @@ class DictionaryField(models.Field):
         return self.get_prep_value(value)
 
     def formfield(self, **kwargs):
-        defaults = {'widget': forms.Textarea}
+        defaults = {"widget": forms.Textarea}
         defaults.update(kwargs)
         return super().formfield(**defaults)
 
 
 # From https://djangosnippets.org/snippets/1478/
 
+
 class JSONField(models.TextField):
     """JSONField is a generic textfield that neatly serializes/unserializes
     JSON objects seamlessly"""
diff --git a/common/middleware.py b/common/middleware.py
index 1708c067..10218de9 100644
--- a/common/middleware.py
+++ b/common/middleware.py
@@ -3,10 +3,10 @@ from django.utils.deprecation import MiddlewareMixin
 
 
 class TokenAuthenticationMiddleware(MiddlewareMixin):
-    backend_path = 'common.backends.TokenBackend'
+    backend_path = "common.backends.TokenBackend"
 
     def process_request(self, request):
-        if request.META.get('HTTP_AUTHENTICATION', '') and request.user.is_anonymous:
+        if request.META.get("HTTP_AUTHENTICATION", "") and request.user.is_anonymous:
             backend = load_backend(self.backend_path)
             user = backend.authenticate(request)
             if user:
diff --git a/common/templatetags/list_to_columns.py b/common/templatetags/list_to_columns.py
index 85889f1c..600ba73c 100644
--- a/common/templatetags/list_to_columns.py
+++ b/common/templatetags/list_to_columns.py
@@ -20,7 +20,7 @@ class SplitListNode(template.Node):
 
     def render(self, context):
         context[self.new_list] = self.split_seq(context[self.list], int(self.cols))
-        return ''
+        return ""
 
 
 def list_to_columns(parser, token):
@@ -28,7 +28,7 @@ def list_to_columns(parser, token):
     bits = token.contents.split()
     if len(bits) != 5:
         raise template.TemplateSyntaxError("list_to_columns list as new_list 2")
-    if bits[2] != 'as':
+    if bits[2] != "as":
         raise template.TemplateSyntaxError("second argument to the list_to_columns tag must be 'as'")
     return SplitListNode(bits[1], bits[4], bits[3])
 
diff --git a/common/tests.py b/common/tests.py
index 6a83a9ca..203d4cb8 100644
--- a/common/tests.py
+++ b/common/tests.py
@@ -11,65 +11,72 @@ from django.urls import reverse
 from django.utils import timezone, translation
 
 from people.models import Person
-from teams.models import Team, Role
+from teams.models import Role, Team
+
 from .utils import lc_sorted, pyicu_present, trans_sort_object_list
 
 
 class CommonTest(DjangoTestCase):
-
     def test_admin_login(self):
-        response = self.client.get(reverse('admin:index'))
-        self.assertRedirects(response, reverse('admin:login') + '?next=' + reverse('admin:index'))
+        response = self.client.get(reverse("admin:index"))
+        self.assertRedirects(response, reverse("admin:login") + "?next=" + reverse("admin:index"))
 
     def test_help_pages(self):
-        response = self.client.get(reverse('help', args=['reduced_po', 1]))
+        response = self.client.get(reverse("help", args=["reduced_po", 1]))
         self.assertContains(response, '<div class="modal-body">')
 
-        response = self.client.get(reverse('help', args=['reduced_po', 0]))
+        response = self.client.get(reverse("help", args=["reduced_po", 0]))
         self.assertNotContains(response, '<div class="modal-body">')
 
-        response = self.client.get(reverse('help', args=['reduced_po']))
+        response = self.client.get(reverse("help", args=["reduced_po"]))
         self.assertNotContains(response, '<div class="modal-body">')
 
-        response = self.client.get(reverse('help', args=['unexisting', 1]))
+        response = self.client.get(reverse("help", args=["unexisting", 1]))
         self.assertEqual(response.status_code, 404)
 
     def test_house_keeping(self):
         pn = Person.objects.create(
-            first_name='John', last_name='Note',
-            email='jn devnull com', username='jn', activation_key='a_activation_key'
+            first_name="John",
+            last_name="Note",
+            email="jn devnull com",
+            username="jn",
+            activation_key="a_activation_key",
         )
-        pn.set_password('password')
+        pn.set_password("password")
 
         # Unactivated person
         Person.objects.create(
-            first_name='John', last_name='Reviewer',
-            username='jr', date_joined=timezone.now() - timedelta(days=11),
-            activation_key='non-activated-key'
+            first_name="John",
+            last_name="Reviewer",
+            username="jr",
+            date_joined=timezone.now() - timedelta(days=11),
+            activation_key="non-activated-key",
         )
 
         inactive = Person.objects.create(
-            first_name='John', last_name='Translator',
-            username='jt', last_login=timezone.now() - timedelta(days=30 * 6 + 1),
+            first_name="John",
+            last_name="Translator",
+            username="jt",
+            last_login=timezone.now() - timedelta(days=30 * 6 + 1),
         )
 
-        t1 = Team.objects.create(name='fr', description='French')
-        t2 = Team.objects.create(name='fr2', description='French')
+        t1 = Team.objects.create(name="fr", description="French")
+        t2 = Team.objects.create(name="fr2", description="French")
 
         Role.objects.create(team=t1, person=inactive)
         Role.objects.create(team=t2, person=inactive)
 
-        response = self.client.get('/register/activate/a_activation_key', follow=True)
-        self.assertContains(response, 'Your account has been activated.')
+        response = self.client.get("/register/activate/a_activation_key", follow=True)
+        self.assertContains(response, "Your account has been activated.")
 
-        call_command('run-maintenance')
+        call_command("run-maintenance")
 
         # Testing if the non-activated accounts were deleted
-        self.assertTrue(Person.objects.filter(last_name='Note').exists())
-        self.assertFalse(Person.objects.filter(last_name='Reviewer').exists())
+        self.assertTrue(Person.objects.filter(last_name="Note").exists())
+        self.assertFalse(Person.objects.filter(last_name="Reviewer").exists())
 
         # Testing if the inactive roles were updated
-        jt = Person.objects.get(first_name='John', last_name='Translator')
+        jt = Person.objects.get(first_name="John", last_name="Translator")
         for role in Role.objects.filter(person=jt):
             self.assertFalse(role.is_active)
 
@@ -79,78 +86,74 @@ class CommonTest(DjangoTestCase):
             "ref": "refs/heads/master",
             # Gitlab will send a lot more, but we don't care.
         }
-        with patch('common.views.run_shell_command', return_value=(0, '', '')) as rsc_patched:
-            with patch('common.views.call_command') as cc_patched:
-                response = self.client.post('/pull_code/', data=push_data)
+        with patch("common.views.run_shell_command", return_value=(0, "", "")) as rsc_patched:
+            with patch("common.views.call_command") as cc_patched:
+                response = self.client.post("/pull_code/", data=push_data)
                 self.assertEqual(response.status_code, 403)
                 response = self.client.post(
-                    '/pull_code/', data=push_data,
-                    HTTP_X_GITLAB_EVENT='Push Hook',
+                    "/pull_code/",
+                    data=push_data,
+                    HTTP_X_GITLAB_EVENT="Push Hook",
                     HTTP_X_GITLAB_TOKEN=settings.GITLAB_TOKEN,
                 )
         self.assertEqual(rsc_patched.call_count, 2)
         self.assertEqual(cc_patched.call_count, 1)
-        self.assertEqual(response.content, b'OK')
+        self.assertEqual(response.content, b"OK")
 
 
 class LcSortedTest(TestCase):
-
     @skipUnless(pyicu_present, "PyICU package is required for this test")
     def test_lc_sorted_en_case_insensitive(self):
-        with translation.override('en'):
-            self.assertEqual(['A', 'b', 'C', 'z'], lc_sorted(['z', 'C', 'b', 'A']))
+        with translation.override("en"):
+            self.assertEqual(["A", "b", "C", "z"], lc_sorted(["z", "C", "b", "A"]))
 
     @skipUnless(pyicu_present, "PyICU package is required for this test")
     def test_lc_sorted_fr_eacute_between_d_and_f(self):
-        with translation.override('fr'):
-            self.assertEqual(['d', 'é', 'f', 'z'], lc_sorted(['é', 'z', 'd', 'f']))
+        with translation.override("fr"):
+            self.assertEqual(["d", "é", "f", "z"], lc_sorted(["é", "z", "d", "f"]))
 
     @skipUnless(pyicu_present, "PyICU package is required for this test")
     def test_lc_sorted_sv_ouml_after_z(self):
-        with translation.override('sv'):
-            self.assertEqual(['a', 'o', 'z', 'ö'], lc_sorted(['z', 'ö', 'o', 'a']))
+        with translation.override("sv"):
+            self.assertEqual(["a", "o", "z", "ö"], lc_sorted(["z", "ö", "o", "a"]))
 
     def test_lc_sorted_with_custom_key(self):
         mykey = operator.itemgetter(1)
-        with translation.override('fr'):
-            self.assertEqual(
-                [('z', 'a'), ('a', 'z')],
-                lc_sorted([('a', 'z'), ('z', 'a')], key=mykey)
-            )
+        with translation.override("fr"):
+            self.assertEqual([("z", "a"), ("a", "z")], lc_sorted([("a", "z"), ("z", "a")], key=mykey))
 
 
 class TransSortObjectListTest(TestCase):
-
     def make_item(self, name):
         item = MagicMock(name=name)
         item.name = name
         return item
 
-    @patch('common.utils._', return_value="bar")
+    @patch("common.utils._", return_value="bar")
     def test_translate_object_name(self, gettext):
         """
         The given field is translated using gettext and the translation is stored
         in translated_name.
         """
         item = self.make_item("foo")
-        trans_sort_object_list([item], 'name')
+        trans_sort_object_list([item], "name")
         self.assertEqual("bar", item.translated_name)
 
-    @patch('common.utils._', side_effect=lambda s: s[::-1])
+    @patch("common.utils._", side_effect=lambda s: s[::-1])
     def test_sort_list_according_to_translation(self, _):
         """
         The list is sorted according to the computed translated_name
         (here gettext reverses the string so foo (oof) comes before bar (rab)).
         """
-        foo, bar = self.make_item('foo'), self.make_item('bar')
-        actual = trans_sort_object_list([foo, bar], 'name')
+        foo, bar = self.make_item("foo"), self.make_item("bar")
+        actual = trans_sort_object_list([foo, bar], "name")
         self.assertEqual([foo, bar], actual, "wrong order")
 
     @skipUnless(pyicu_present, "PyICU package is required for this test")
-    @patch('common.utils._', side_effect=lambda x: x)
+    @patch("common.utils._", side_effect=lambda x: x)
     def test_uses_localized_ordering(self, _):
         """Consider the current locale when ordering elements."""
-        d, eacute, f = self.make_item('d'), self.make_item('é'), self.make_item('f')
-        with translation.override('fr'):
-            actual = trans_sort_object_list([f, eacute, d], 'name')
+        d, eacute, f = self.make_item("d"), self.make_item("é"), self.make_item("f")
+        with translation.override("fr"):
+            actual = trans_sort_object_list([f, eacute, d], "name")
             self.assertEqual([d, eacute, f], actual)
diff --git a/common/utils.py b/common/utils.py
index f008f51f..aac776d4 100644
--- a/common/utils.py
+++ b/common/utils.py
@@ -5,45 +5,47 @@ from subprocess import PIPE, run
 
 from django.conf import settings
 from django.core.mail import EmailMessage
-from django.utils.translation import gettext as _, get_language
+from django.utils.translation import get_language
+from django.utils.translation import gettext as _
 
 try:
     import icu
+
     pyicu_present = True
 except ImportError:
     pyicu_present = False
 
-MIME_TYPES = {
-    'json': 'application/json',
-    'xml':  'text/xml'
-}
+MIME_TYPES = {"json": "application/json", "xml": "text/xml"}
 STATUS_OK = 0
 
 
-def run_shell_command(cmd, input_data=None, raise_on_error=False, env=None,
-                      cwd=None, **extra_kwargs):
+def run_shell_command(cmd, input_data=None, raise_on_error=False, env=None, cwd=None, **extra_kwargs):
     logging.debug(cmd)
 
     if env is not None:
         env = dict(os.environ, **env)
     shell = not isinstance(cmd, list)
     result = run(
-        cmd, shell=shell, input=input_data, encoding='utf-8',
-        stdout=PIPE, stderr=PIPE, env=env, cwd=cwd, **extra_kwargs,
+        cmd,
+        shell=shell,
+        input=input_data,
+        encoding="utf-8",
+        stdout=PIPE,
+        stderr=PIPE,
+        env=env,
+        cwd=cwd,
+        **extra_kwargs,
     )
     status = result.returncode
     logging.debug(result.stdout + result.stderr)
     if raise_on_error and status != STATUS_OK:
-        is_git_command = cmd.startswith('git') if isinstance(cmd, str) else cmd[0] == 'git'
+        is_git_command = cmd.startswith("git") if isinstance(cmd, str) else cmd[0] == "git"
         if is_git_command and status == -25:
             # Try to reset the git repository to an usable state
-            run_shell_command(['rm', '.git/index.lock'], cwd=cwd)
-            run_shell_command(['git', 'clean', '-dfq'], cwd=cwd)
-            run_shell_command(['git', 'reset', '--hard'], cwd=cwd)
-        raise OSError(
-            status,
-            'Command: "%s", Error: %s' % (cmd, result.stderr or result.stdout)
-        )
+            run_shell_command(["rm", ".git/index.lock"], cwd=cwd)
+            run_shell_command(["git", "clean", "-dfq"], cwd=cwd)
+            run_shell_command(["git", "reset", "--hard"], cwd=cwd)
+        raise OSError(status, 'Command: "%s", Error: %s' % (cmd, result.stderr or result.stdout))
 
     return (status, result.stdout, result.stderr)
 
@@ -54,8 +56,8 @@ def lc_sorted(*args, **kwargs):
     """
     if pyicu_present:
         collator = icu.Collator.createInstance(icu.Locale(str(get_language())))
-        key = kwargs.get('key', lambda x: x)
-        kwargs['key'] = lambda x: collator.getSortKey(key(x))
+        key = kwargs.get("key", lambda x: x)
+        kwargs["key"] = lambda x: collator.getSortKey(key(x))
     return sorted(*args, **kwargs)
 
 
@@ -74,7 +76,7 @@ def get_user_locale(request):
     from languages.models import Language  # Import here to prevent loop
 
     curlang = Language.get_language_from_ianacode(request.LANGUAGE_CODE)
-    if curlang and curlang.locale == 'en':
+    if curlang and curlang.locale == "en":
         curlang = None
     return curlang
 
@@ -82,25 +84,24 @@ def get_user_locale(request):
 def send_mail(subject, message, **kwargs):
     """Wrapper to Django's send_mail allowing all EmailMessage init arguments."""
     if not subject.startswith(settings.EMAIL_SUBJECT_PREFIX):
-        subject = '%s%s' % (settings.EMAIL_SUBJECT_PREFIX, subject)
+        subject = "%s%s" % (settings.EMAIL_SUBJECT_PREFIX, subject)
     EmailMessage(subject, message, **kwargs).send()
 
 
 def check_gitlab_request(request):
-    remote_ip = request.META.get('HTTP_X_FORWARDED_FOR', request.META.get('REMOTE_ADDR'))
-    if ',' in remote_ip:
-        remote_ip = remote_ip.split(',')[0].strip()
+    remote_ip = request.META.get("HTTP_X_FORWARDED_FOR", request.META.get("REMOTE_ADDR"))
+    if "," in remote_ip:
+        remote_ip = remote_ip.split(",")[0].strip()
     try:
         from_host = socket.gethostbyaddr(remote_ip)[0]
     except socket.herror:
         return False
     except Exception:
         raise Exception("Unable to get host for address '%s'" % remote_ip)
-    return (
-        request.method == 'POST' and (
-            from_host == 'gitlab.gnome.org' or (
-                request.META.get('HTTP_X_GITLAB_EVENT') == 'Push Hook' and
-                request.META.get('HTTP_X_GITLAB_TOKEN') == settings.GITLAB_TOKEN
-            )
+    return request.method == "POST" and (
+        from_host == "gitlab.gnome.org"
+        or (
+            request.META.get("HTTP_X_GITLAB_EVENT") == "Push Hook"
+            and request.META.get("HTTP_X_GITLAB_TOKEN") == settings.GITLAB_TOKEN
         )
     )
diff --git a/common/views.py b/common/views.py
index 65fcbd29..43178ded 100644
--- a/common/views.py
+++ b/common/views.py
@@ -2,70 +2,78 @@ from smtplib import SMTPException
 from threading import Thread
 
 from django.conf import settings
-from django.contrib.auth.views import LoginView as AuthLoginView
 from django.contrib import messages
+from django.contrib.auth.views import LoginView as AuthLoginView
 from django.core.management import call_command
-from django.http import HttpResponse, HttpResponseForbidden, HttpResponseRedirect, Http404
+from django.http import (
+    Http404,
+    HttpResponse,
+    HttpResponseForbidden,
+    HttpResponseRedirect,
+)
 from django.shortcuts import render
-from django.template.loader import get_template, TemplateDoesNotExist
+from django.template.loader import TemplateDoesNotExist, get_template
 from django.urls import reverse
 from django.utils.translation import gettext as _
 from django.views.decorators.csrf import csrf_exempt
 from django.views.decorators.debug import sensitive_variables
 
+from people.forms import LoginForm, RegistrationForm
 from people.models import Person, obfuscate_email
 from teams.models import Role
-from people.forms import LoginForm, RegistrationForm
+
 from .utils import check_gitlab_request, get_user_locale, run_shell_command
 
 
 def index(request):
-    """ Homepage view """
+    """Homepage view"""
     curlang = get_user_locale(request)
 
     context = {
-        'pageSection': 'home',
-        'user_language': curlang,
+        "pageSection": "home",
+        "user_language": curlang,
     }
-    return render(request, 'index.html', context)
+    return render(request, "index.html", context)
 
 
 def about(request):
     translator_credits = _("translator-credits")
     if translator_credits == "translator-credits":
-        translator_credits = ''
+        translator_credits = ""
     else:
-        translator_credits = [obfuscate_email(line) for line in translator_credits.split('\n')]
+        translator_credits = [obfuscate_email(line) for line in translator_credits.split("\n")]
     context = {
-        'damned_lies_version': settings.VERSION or "unknown",
-        'pageSection': 'home',
-        'translator_credits': translator_credits,
+        "damned_lies_version": settings.VERSION or "unknown",
+        "pageSection": "home",
+        "translator_credits": translator_credits,
     }
-    return render(request, 'about.html', context)
+    return render(request, "about.html", context)
 
 
 class LoginView(AuthLoginView):
     form_class = LoginForm
-    redirect_field_name = 'referer'
+    redirect_field_name = "referer"
 
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
-        if not context['referer']:
-            context['referer'] = self.request.META.get('HTTP_REFERER', '')
+        if not context["referer"]:
+            context["referer"] = self.request.META.get("HTTP_REFERER", "")
         return context
 
     def get_redirect_url(self):
         url = super().get_redirect_url()
         if not url:
-            url = self.request.META.get('HTTP_REFERER', '')
+            url = self.request.META.get("HTTP_REFERER", "")
         return url
 
     def form_valid(self, form):
         response = super().form_valid(form)
         if Role.objects.filter(person__username=self.request.user.username).count() < 1:
-            message = _("You have not joined any translation team yet. "
-                        "You can do it from <a href=\"%(url)s\">your profile</a>.") % {
-                'url': reverse('person_team_join'),
+            message = _(
+                "You have not joined any translation team yet. "
+                'You can do it from <a href="%(url)s">your profile</a>.'
+            ) % {
+                "url": reverse("person_team_join"),
             }
             messages.info(self.request, message)
         return response
@@ -75,48 +83,51 @@ class LoginView(AuthLoginView):
         return super().form_invalid(form)
 
 
-@sensitive_variables('password1', 'password2')
+@sensitive_variables("password1", "password2")
 def site_register(request):
-    if request.method == 'POST':
+    if request.method == "POST":
         form = RegistrationForm(data=request.POST)
         if form.is_valid():
             try:
                 form.save(request)
             except SMTPException as exc:
-                messages.error(request, _("An error occurred while sending mail to {email} ({err})".format(
-                    email=form.cleaned_data['email'], err=str(exc)
-                )))
+                messages.error(
+                    request,
+                    _(
+                        "An error occurred while sending mail to {email} ({err})".format(
+                            email=form.cleaned_data["email"], err=str(exc)
+                        )
+                    ),
+                )
             else:
-                return HttpResponseRedirect(reverse('register_success'))
+                return HttpResponseRedirect(reverse("register_success"))
     else:
         form = RegistrationForm()
     context = {
-        'pageSection': 'home',
-        'form': form,
+        "pageSection": "home",
+        "form": form,
     }
-    return render(request, 'registration/register.html', context)
+    return render(request, "registration/register.html", context)
 
 
 def activate_account(request, key):
-    """ Activate an account through the link a requestor has received by email """
+    """Activate an account through the link a requestor has received by email"""
     try:
         person = Person.objects.get(activation_key=key)
     except Person.DoesNotExist:
-        return render(request, 'error.html', {'error': _("Sorry, the key you provided is not valid.")})
+        return render(request, "error.html", {"error": _("Sorry, the key you provided is not valid.")})
     person.activate()
     messages.success(request, _("Your account has been activated."))
-    return HttpResponseRedirect(reverse('login'))
+    return HttpResponseRedirect(reverse("login"))
 
 
 def help(request, topic, modal=None):
-    template = 'help/%s.html' % topic
+    template = "help/%s.html" % topic
     try:
         get_template(template)
     except TemplateDoesNotExist:
         raise Http404
-    return render(request, template, {
-        'base': 'base_modal.html' if modal and int(modal) else 'base.html'
-    })
+    return render(request, template, {"base": "base_modal.html" if modal and int(modal) else "base.html"})
 
 
 # CSRF skipped, verification using a secret token.
@@ -130,11 +141,11 @@ def pull_code(request):
     # Run effective work in a separate thread to prevent timeouts
     thread = Thread(target=pull_code_real)
     thread.start()
-    return HttpResponse('OK')
+    return HttpResponse("OK")
 
 
 def pull_code_real():
-    cwd = settings.BASE_DIR / 'damnedlies'
-    run_shell_command(['git', 'pull', '--rebase'], cwd=cwd)
-    call_command('compile-trans', verbosity=0)
-    run_shell_command(['touch', 'wsgi.py'], cwd=cwd)
+    cwd = settings.BASE_DIR / "damnedlies"
+    run_shell_command(["git", "pull", "--rebase"], cwd=cwd)
+    call_command("compile-trans", verbosity=0)
+    run_shell_command(["touch", "wsgi.py"], cwd=cwd)
diff --git a/damnedlies/context_processors.py b/damnedlies/context_processors.py
index f612c7d2..af300b2b 100644
--- a/damnedlies/context_processors.py
+++ b/damnedlies/context_processors.py
@@ -1,4 +1,3 @@
-
 def running_integration_instance(request):
     running_production_system = False
     try:
diff --git a/damnedlies/settings.py b/damnedlies/settings.py
index b51d0168..108ce8fe 100644
--- a/damnedlies/settings.py
+++ b/damnedlies/settings.py
@@ -3,6 +3,7 @@ import os
 from pathlib import Path
 
 from django.conf import global_settings
+
 gettext_noop = lambda s: s
 
 DEBUG = True
@@ -11,64 +12,62 @@ USE_DEBUG_TOOLBAR = False
 
 BASE_DIR = Path(__file__).resolve().parent.parent
 
-ADMINS = (
-    ('Your Name', 'your_address example org'),
-)
+ADMINS = (("Your Name", "your_address example org"),)
 
 DATABASES = {
-    'default': {
-        'ENGINE': 'django.db.backends.sqlite3',
-        'NAME': BASE_DIR / 'database.db',
+    "default": {
+        "ENGINE": "django.db.backends.sqlite3",
+        "NAME": BASE_DIR / "database.db",
     }
 }
 # Please refer to the README file to create an UTF-8 database with MySQL.
-DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
-
-EMAIL_HOST = 'localhost'
-EMAIL_HOST_USER = ''
-EMAIL_HOST_PASSWORD = ''
-EMAIL_SUBJECT_PREFIX = '[Damned Lies] '
-EMAIL_HEADER_NAME = 'X-Vertimus'
-DEFAULT_FROM_EMAIL = 'gnomeweb gnome org'
-SERVER_EMAIL = 'gnomeweb gnome org'
+DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
+
+EMAIL_HOST = "localhost"
+EMAIL_HOST_USER = ""
+EMAIL_HOST_PASSWORD = ""
+EMAIL_SUBJECT_PREFIX = "[Damned Lies] "
+EMAIL_HEADER_NAME = "X-Vertimus"
+DEFAULT_FROM_EMAIL = "gnomeweb gnome org"
+SERVER_EMAIL = "gnomeweb gnome org"
 # When in STRINGFREEZE, where to send notifications (gnome-i18n gnome org) on any POT changes
-NOTIFICATIONS_TO = ['gnome-i18n gnome org']
-ENTER_BUG_URL = 'https://gitlab.gnome.org/Infrastructure/damned-lies/issues'
-ENTER_LANGUAGE_BUG_URL = 'https://gitlab.gnome.org/Teams/Translation/%(lang)s/issues/new'
-BROWSE_LANGUAGE_BUG_URL = 'https://gitlab.gnome.org/Teams/Translation/%(lang)s/issues?state=opened'
+NOTIFICATIONS_TO = ["gnome-i18n gnome org"]
+ENTER_BUG_URL = "https://gitlab.gnome.org/Infrastructure/damned-lies/issues";
+ENTER_LANGUAGE_BUG_URL = "https://gitlab.gnome.org/Teams/Translation/%(lang)s/issues/new"
+BROWSE_LANGUAGE_BUG_URL = "https://gitlab.gnome.org/Teams/Translation/%(lang)s/issues?state=opened"
 
 # Local time zone for this installation. Choices can be found here:
 # https://en.wikipedia.org/wiki/List_of_tz_zones_by_name
 # although not all choices may be available on all operating systems.
 # If running in a Windows environment this must be set to the same as your
 # system time zone.
-TIME_ZONE = 'Europe/Zurich'
+TIME_ZONE = "Europe/Zurich"
 
 # Language code for this installation. All choices can be found here:
 # http://www.i18nguy.com/unicode/language-identifiers.html
-LANGUAGE_CODE = 'en'
+LANGUAGE_CODE = "en"
 LANGUAGES = global_settings.LANGUAGES + [
     # Add here languages with translations for D-L but not for Django
-    ('fur', gettext_noop('Friulian')),
-    ('gu', gettext_noop('Gujarati')),
-    ('ku', gettext_noop('Kurdish')),
+    ("fur", gettext_noop("Friulian")),
+    ("gu", gettext_noop("Gujarati")),
+    ("ku", gettext_noop("Kurdish")),
 ]
 
 USE_I18N = True
 USE_L10N = True
-LOCALE_PATHS = [BASE_DIR / 'locale']
+LOCALE_PATHS = [BASE_DIR / "locale"]
 
 USE_TZ = True
 
 # Absolute path to the directory that holds media.
-MEDIA_ROOT = BASE_DIR / 'media'
+MEDIA_ROOT = BASE_DIR / "media"
 
 # URL that handles the media served from MEDIA_ROOT.
-MEDIA_URL = '/media/'
+MEDIA_URL = "/media/"
 
-STATIC_ROOT = BASE_DIR / 'static'
+STATIC_ROOT = BASE_DIR / "static"
 
-STATIC_URL = '/static/'
+STATIC_URL = "/static/"
 
 # Local directory path for VCS checkout
 SCRATCHDIR = Path()
@@ -82,116 +81,113 @@ VCS_HOME_WARNING = gettext_noop(
 )
 
 # By default, Django stores files locally, using the MEDIA_ROOT and MEDIA_URL settings
-UPLOAD_DIR = 'upload'
-UPLOAD_ARCHIVED_DIR = 'upload-archived'
+UPLOAD_DIR = "upload"
+UPLOAD_ARCHIVED_DIR = "upload-archived"
 FILE_UPLOAD_PERMISSIONS = 0o600
 
-LOGIN_URL = '/login/'
+LOGIN_URL = "/login/"
 
 # Make this unique, and don't share it with anybody.
 # To be filled in local_settings.py
-SECRET_KEY = ''
+SECRET_KEY = ""
 
 TEMPLATES = [
     {
-        'BACKEND': 'django.template.backends.django.DjangoTemplates',
-        'DIRS': [BASE_DIR / 'templates'],
-        'APP_DIRS': True,
-        'OPTIONS': {
-            'context_processors': [
+        "BACKEND": "django.template.backends.django.DjangoTemplates",
+        "DIRS": [BASE_DIR / "templates"],
+        "APP_DIRS": True,
+        "OPTIONS": {
+            "context_processors": [
                 # Default list:
-                'django.contrib.auth.context_processors.auth',
-                'django.template.context_processors.debug',
-                'django.template.context_processors.i18n',
-                'django.template.context_processors.media',
-                'django.template.context_processors.static',
-                'django.template.context_processors.tz',
-                'django.contrib.messages.context_processors.messages',
+                "django.contrib.auth.context_processors.auth",
+                "django.template.context_processors.debug",
+                "django.template.context_processors.i18n",
+                "django.template.context_processors.media",
+                "django.template.context_processors.static",
+                "django.template.context_processors.tz",
+                "django.contrib.messages.context_processors.messages",
                 # Added processors:
-                'django.template.context_processors.request',
-                'common.context_processors.utils',
-                'damnedlies.context_processors.running_integration_instance',
+                "django.template.context_processors.request",
+                "common.context_processors.utils",
+                "damnedlies.context_processors.running_integration_instance",
             ],
-            'builtins': [
-                'django.templatetags.i18n',
-                'django.templatetags.static',
+            "builtins": [
+                "django.templatetags.i18n",
+                "django.templatetags.static",
             ],
         },
     },
 ]
 
 MIDDLEWARE = [
-    'django.contrib.sessions.middleware.SessionMiddleware',
-    'django.middleware.locale.LocaleMiddleware',
-    'django.middleware.common.CommonMiddleware',
-    'django.middleware.csrf.CsrfViewMiddleware',
-    'django.contrib.auth.middleware.AuthenticationMiddleware',
-    'common.middleware.TokenAuthenticationMiddleware',
-    'django.contrib.messages.middleware.MessageMiddleware',
-    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+    "django.contrib.sessions.middleware.SessionMiddleware",
+    "django.middleware.locale.LocaleMiddleware",
+    "django.middleware.common.CommonMiddleware",
+    "django.middleware.csrf.CsrfViewMiddleware",
+    "django.contrib.auth.middleware.AuthenticationMiddleware",
+    "common.middleware.TokenAuthenticationMiddleware",
+    "django.contrib.messages.middleware.MessageMiddleware",
+    "django.middleware.clickjacking.XFrameOptionsMiddleware",
 ]
 
 ATOMIC_REQUESTS = True
 
-ROOT_URLCONF = 'damnedlies.urls'
+ROOT_URLCONF = "damnedlies.urls"
 
 INSTALLED_APPS = [
-    'django.contrib.auth',
-    'django.contrib.contenttypes',
-    'django.contrib.sessions',
-    'django.contrib.admin',
-    'django.contrib.humanize',
-    'django.contrib.messages',
-    'django.contrib.staticfiles',
-    'common',
-    'languages',
-    'people',
-    'stats',
-    'teams',
-    'vertimus',
-    'feeds',
-    'api',
+    "django.contrib.auth",
+    "django.contrib.contenttypes",
+    "django.contrib.sessions",
+    "django.contrib.admin",
+    "django.contrib.humanize",
+    "django.contrib.messages",
+    "django.contrib.staticfiles",
+    "common",
+    "languages",
+    "people",
+    "stats",
+    "teams",
+    "vertimus",
+    "feeds",
+    "api",
 ]
 
-INTERNAL_IPS = ('127.0.0.1',)
+INTERNAL_IPS = ("127.0.0.1",)
 
-SITE_DOMAIN = 'l10n.gnome.org'
+SITE_DOMAIN = "l10n.gnome.org"
 
-LOGIN_REDIRECT_URL = '/'
+LOGIN_REDIRECT_URL = "/"
 
 # Members of this group can edit all team's details and change team coordinatorship
-ADMIN_GROUP = ''
+ADMIN_GROUP = ""
 
 # https://www.gnu.org/software/gettext/manual/html_node/Preparing-ITS-Rules.html#Preparing-ITS-Rules
 GETTEXT_ITS_DATA = {
-    'gtk': [
-        os.path.join('tools', 'gtk4builder.its'),
-        os.path.join('tools', 'gtk4builder.loc')
-    ],
-    'glib': [os.path.join('gio', 'gschema.its'), os.path.join('gio', 'gschema.loc')],
-    'gnome-control-center': [
-        os.path.join('gettext', 'its', 'gnome-keybindings.its'),
-        os.path.join('gettext', 'its', 'gnome-keybindings.loc'),
+    "gtk": [os.path.join("tools", "gtk4builder.its"), os.path.join("tools", "gtk4builder.loc")],
+    "glib": [os.path.join("gio", "gschema.its"), os.path.join("gio", "gschema.loc")],
+    "gnome-control-center": [
+        os.path.join("gettext", "its", "gnome-keybindings.its"),
+        os.path.join("gettext", "its", "gnome-keybindings.loc"),
     ],
-    'polkit': [
-        os.path.join('gettext', 'its', 'polkit.its'),
-        os.path.join('gettext', 'its', 'polkit.loc'),
+    "polkit": [
+        os.path.join("gettext", "its", "polkit.its"),
+        os.path.join("gettext", "its", "polkit.loc"),
     ],
-    'shared-mime-info': [
-        os.path.join('data', 'its', 'shared-mime-info.its'),
-        os.path.join('data', 'its', 'shared-mime-info.loc'),
+    "shared-mime-info": [
+        os.path.join("data", "its", "shared-mime-info.its"),
+        os.path.join("data", "its", "shared-mime-info.loc"),
     ],
     # Copy of https://github.com/ximion/appstream/tree/master/data/its (2020-02-02)
     # minus the release/description line (#149).
-    'damned-lies': [
-        os.path.join('common', 'static', 'metainfo.its'),
-        os.path.join('common', 'static', 'metainfo.loc'),
+    "damned-lies": [
+        os.path.join("common", "static", "metainfo.its"),
+        os.path.join("common", "static", "metainfo.loc"),
     ],
 }
 
-GITLAB_TOKEN = 'fill_with_real_token'
+GITLAB_TOKEN = "fill_with_real_token"
 
-LOCK_DIR = Path('/var/lock')
+LOCK_DIR = Path("/var/lock")
 
 try:
     from .local_settings import *
@@ -211,14 +207,14 @@ LOGGING = {
             "handlers": ["console"],
             "level": "ERROR",
         },
-    }
+    },
 }
 
 # When running in development mode, print emails on stdout instead of trying
 # to send them
 if DEBUG:
-    EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
+    EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
 
 if USE_DEBUG_TOOLBAR:
-    MIDDLEWARE.insert(0, 'debug_toolbar.middleware.DebugToolbarMiddleware')
-    INSTALLED_APPS.append('debug_toolbar')
+    MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware")
+    INSTALLED_APPS.append("debug_toolbar")
diff --git a/damnedlies/settings_tests.py b/damnedlies/settings_tests.py
index 99eb87c7..880a7f0e 100644
--- a/damnedlies/settings_tests.py
+++ b/damnedlies/settings_tests.py
@@ -2,18 +2,18 @@ import tempfile
 
 from .settings import *
 
-SECRET_KEY = 'shRc(?sk+sW3Wqn-lBvs=r52a@#hgC9g'
+SECRET_KEY = "shRc(?sk+sW3Wqn-lBvs=r52a@#hgC9g"
 
-SCRATCHDIR = BASE_DIR / 'scratch'
-POTDIR = SCRATCHDIR / 'POT'
+SCRATCHDIR = BASE_DIR / "scratch"
+POTDIR = SCRATCHDIR / "POT"
 GETTEXT_ITS_DATA = {}
 
 LOCK_DIR = Path(tempfile.mkdtemp())
 
 try:
     import xmlrunner
+
     TEST_RUNNER = "xmlrunner.extra.djangotestrunner.XMLTestRunner"
     TEST_OUTPUT_DIR = open("tests-report.xml", "wb")
 except ImportError:
     pass
-
diff --git a/damnedlies/urls.py b/damnedlies/urls.py
index 8065e56a..0b2ad1b0 100644
--- a/damnedlies/urls.py
+++ b/damnedlies/urls.py
@@ -12,119 +12,89 @@ from stats import views as stats_views
 
 
 class LocaleConverter(StringConverter):
-    regex = '[-_a-zA-Z@]+'
+    regex = "[-_a-zA-Z@]+"
 
 
 class NameConverter(StringConverter):
     """Converter for module, branch, or domain names."""
-    regex = r'[-~\w\+\.]+'
 
+    regex = r"[-~\w\+\.]+"
 
-register_converter(LocaleConverter, 'locale')
-register_converter(NameConverter, 'name')
-module_branch_domain = '<name:module_name>/<path:branch_name>/<name:domain_name>'
 
-urlpatterns = [
-    path('',
-         common_views.index,
-         name='home'),
-    path('about/',
-         common_views.about,
-         name='about'),
-    path('login/',
-         common_views.LoginView.as_view(template_name='login.html'),
-         name='login'),
-    path('logout/',
-         require_POST(auth_views.LogoutView.as_view()),
-         name='logout'),
-    path('register/',
-         common_views.site_register,
-         name='register'),
-    re_path(r'^help/(?P<topic>\w+)/(?P<modal>[0-1])?/?$',
-            common_views.help,
-            name='help'),
-    path('register/success/',
-         TemplateView.as_view(template_name="registration/register_success.html"),
-         name='register_success'),
-    path('register/activate/<slug:key>',
-         common_views.activate_account,
-         name='register_activation'),
-    path('password_reset/',
-         auth_views.PasswordResetView.as_view(template_name='registration/password_reset_form.html'),
-         name='password_reset'),
-    path('password_reset/done/',
-         auth_views.PasswordResetDoneView.as_view(template_name='registration/password_reset_done.html'),
-         name='password_reset_done'),
-    path('reset/<slug:uidb64>/<slug:token>/',
-         auth_views.PasswordResetConfirmView.as_view(),
-         name='password_reset_confirm'),
-    path('reset/done/',
-         auth_views.PasswordResetCompleteView.as_view(),
-         name='password_reset_complete'),
+register_converter(LocaleConverter, "locale")
+register_converter(NameConverter, "name")
+module_branch_domain = "<name:module_name>/<path:branch_name>/<name:domain_name>"
 
+urlpatterns = [
+    path("", common_views.index, name="home"),
+    path("about/", common_views.about, name="about"),
+    path("login/", common_views.LoginView.as_view(template_name="login.html"), name="login"),
+    path("logout/", require_POST(auth_views.LogoutView.as_view()), name="logout"),
+    path("register/", common_views.site_register, name="register"),
+    re_path(r"^help/(?P<topic>\w+)/(?P<modal>[0-1])?/?$", common_views.help, name="help"),
+    path(
+        "register/success/",
+        TemplateView.as_view(template_name="registration/register_success.html"),
+        name="register_success",
+    ),
+    path("register/activate/<slug:key>", common_views.activate_account, name="register_activation"),
+    path(
+        "password_reset/",
+        auth_views.PasswordResetView.as_view(template_name="registration/password_reset_form.html"),
+        name="password_reset",
+    ),
+    path(
+        "password_reset/done/",
+        auth_views.PasswordResetDoneView.as_view(template_name="registration/password_reset_done.html"),
+        name="password_reset_done",
+    ),
+    path(
+        "reset/<slug:uidb64>/<slug:token>/",
+        auth_views.PasswordResetConfirmView.as_view(),
+        name="password_reset_confirm",
+    ),
+    path("reset/done/", auth_views.PasswordResetCompleteView.as_view(), name="password_reset_complete"),
     # Webhook endpoint
-    path('pull_code/', common_views.pull_code),
-
-    path('teams/', include('teams.urls')),
-    path('people/', include('people.urls')),
+    path("pull_code/", common_views.pull_code),
+    path("teams/", include("teams.urls")),
+    path("people/", include("people.urls")),
     # users is the hardcoded url in the contrib.auth User class, making it identical to /people
-    path('users/', include('people.urls')),
-    path('languages/', include('languages.urls')),
-    path('vertimus/', include('vertimus.urls')),
-    path('i18n/', include('django.conf.urls.i18n')),
-    path('admin/', admin.site.urls),
-    path('rss/', include('feeds.urls')),
-    path('api/v1/', include('api.urls')),
+    path("users/", include("people.urls")),
+    path("languages/", include("languages.urls")),
+    path("vertimus/", include("vertimus.urls")),
+    path("i18n/", include("django.conf.urls.i18n")),
+    path("admin/", admin.site.urls),
+    path("rss/", include("feeds.urls")),
+    path("api/v1/", include("api.urls")),
 ]
 
 urlpatterns += [
-    re_path(r'^module/(?P<format>(html|json|xml))?/?$',
-            stats_views.modules,
-            name='modules'),
-    path('module/po/%s/<filename>' % module_branch_domain,
-         stats_views.dynamic_po,
-         name='dynamic_po'),
-    path('module/<name:module_name>/',
-         stats_views.module,
-         name='module'),
-    path('module/<name:module_name>/edit/branches/',
-         stats_views.module_edit_branches,
-         name='module_edit_branches'),
-    path('module/<name:module_name>/branch/<path:branch_name>/',
-         stats_views.module_branch,
-         name='module_branch'),
-    path('branch/<int:branch_id>/refresh/',
-         stats_views.branch_refresh,
-         name='branch_refresh'),
-    path('module/<name:module_name>/<name:potbase>/<path:branch_name>/<locale:langcode>/images/',
-         stats_views.docimages,
-         name='docimages'),
-    re_path(r'^releases/(?P<format>(html|json|xml))?/?$',
-            stats_views.releases,
-            name='releases'),
-    re_path(r'^releases/(?P<release_name>[\w-]+)/(?P<format>(html|xml))?/?$',
-            stats_views.release,
-            name='release'),
-    path('releases/compare/<dtype>/<path:rels_to_compare>/',
-         stats_views.compare_by_releases,
-         name='release-compare'),
+    re_path(r"^module/(?P<format>(html|json|xml))?/?$", stats_views.modules, name="modules"),
+    path("module/po/%s/<filename>" % module_branch_domain, stats_views.dynamic_po, name="dynamic_po"),
+    path("module/<name:module_name>/", stats_views.module, name="module"),
+    path("module/<name:module_name>/edit/branches/", stats_views.module_edit_branches, 
name="module_edit_branches"),
+    path("module/<name:module_name>/branch/<path:branch_name>/", stats_views.module_branch, 
name="module_branch"),
+    path("branch/<int:branch_id>/refresh/", stats_views.branch_refresh, name="branch_refresh"),
+    path(
+        "module/<name:module_name>/<name:potbase>/<path:branch_name>/<locale:langcode>/images/",
+        stats_views.docimages,
+        name="docimages",
+    ),
+    re_path(r"^releases/(?P<format>(html|json|xml))?/?$", stats_views.releases, name="releases"),
+    re_path(r"^releases/(?P<release_name>[\w-]+)/(?P<format>(html|xml))?/?$", stats_views.release, 
name="release"),
+    path("releases/compare/<dtype>/<path:rels_to_compare>/", stats_views.compare_by_releases, 
name="release-compare"),
 ]
 
 if settings.USE_DEBUG_TOOLBAR:
     import debug_toolbar
+
     urlpatterns += [
-        path('__debug__/', include(debug_toolbar.urls)),
+        path("__debug__/", include(debug_toolbar.urls)),
     ]
 
 if settings.STATIC_SERVE:
     urlpatterns += [
-        path('media/<path:path>',
-             serve,
-             kwargs={'document_root': settings.MEDIA_ROOT}),
-        path('POT/<path:path>',
-             serve,
-             kwargs={'document_root': settings.POTDIR}),
-        path('HTML/<path:path>',
-             serve,
-             kwargs={'document_root': settings.SCRATCHDIR / 'HTML'}),
+        path("media/<path:path>", serve, kwargs={"document_root": settings.MEDIA_ROOT}),
+        path("POT/<path:path>", serve, kwargs={"document_root": settings.POTDIR}),
+        path("HTML/<path:path>", serve, kwargs={"document_root": settings.SCRATCHDIR / "HTML"}),
     ]
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 7dd797aa..526bb2be 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -5,41 +5,41 @@ import os
 import sys
 
 import django
-from recommonmark.parser import CommonMarkParser
 import sphinx_rtd_theme
+from recommonmark.parser import CommonMarkParser
 
-sys.path.insert(0, os.path.abspath('..'))
-sys.path.insert(1, os.path.abspath('../..'))
+sys.path.insert(0, os.path.abspath(".."))
+sys.path.insert(1, os.path.abspath("../.."))
 
-os.environ['DJANGO_SETTINGS_MODULE'] = 'damnedlies.settings_tests'
+os.environ["DJANGO_SETTINGS_MODULE"] = "damnedlies.settings_tests"
 django.setup()
 
 # -- General configuration ------------------------------------------------
 # Add any Sphinx extension module names here, as strings. They can be
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
-extensions = ['recommonmark', 'sphinx.ext.autodoc']
+extensions = ["recommonmark", "sphinx.ext.autodoc"]
 
 # Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
 
 # The suffix(es) of source filenames.
 # You can specify multiple suffix as a list of string:
-source_parsers = {'.md': CommonMarkParser}
+source_parsers = {".md": CommonMarkParser}
 source_suffix = {
-    '.rst': 'restructuredtext',
-    '.txt': 'markdown',
-    '.md': 'markdown',
+    ".rst": "restructuredtext",
+    ".txt": "markdown",
+    ".md": "markdown",
 }
 
 # The master toctree document.
-master_doc = 'index'
+master_doc = "index"
 
 # General information about the project.
-project = 'damned-lies'
+project = "damned-lies"
 # FIXME: is 2006 correct?
-copyright = '2006 - 2021 − The Damned Lies developers'
-author = 'The Damned Lies developers'
+copyright = "2006 - 2021 − The Damned Lies developers"
+author = "The Damned Lies developers"
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
@@ -56,7 +56,7 @@ release = "master"
 #
 # This is also used if you do content translation via gettext catalogs.
 # Usually you set "language" from the command line for these cases.
-language = 'en'
+language = "en"
 
 # List of patterns, relative to source directory, that match files and
 # directories to ignore when looking for source files.
@@ -64,12 +64,12 @@ language = 'en'
 exclude_patterns = []
 
 # The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = "sphinx"
 
 # If true, `todo` and `todoList` produce output, else they produce nothing.
 todo_include_todos = True
 
-autoclass_content = 'both'
+autoclass_content = "both"
 
 # -- Options for HTML output ----------------------------------------------
 html_theme = "sphinx_rtd_theme"
@@ -77,11 +77,11 @@ html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
 
 html_theme_options = {
     # Toc options
-    'collapse_navigation': False,
-    'sticky_navigation': True,
-    'navigation_depth': 4,
-    'includehidden': True,
-    'titles_only': True
+    "collapse_navigation": False,
+    "sticky_navigation": True,
+    "navigation_depth": 4,
+    "includehidden": True,
+    "titles_only": True,
 }
 
 # Add any paths that contain custom static files (such as style sheets) here,
@@ -92,4 +92,4 @@ html_static_path = []
 # -- Options for HTMLHelp output ------------------------------------------
 
 # Output file base name for HTML help builder.
-htmlhelp_basename = 'damned-lies'
+htmlhelp_basename = "damned-lies"
diff --git a/feeds/urls.py b/feeds/urls.py
index d0053186..1d8a3e3e 100644
--- a/feeds/urls.py
+++ b/feeds/urls.py
@@ -1,8 +1,8 @@
 from django.urls import path
-from vertimus.feeds import LatestActionsByLanguage, LatestActionsByTeam
 
+from vertimus.feeds import LatestActionsByLanguage, LatestActionsByTeam
 
 urlpatterns = [
-    path('languages/<locale:locale>/', LatestActionsByLanguage(), name='lang_feed'),
-    path('teams/<locale:team_name>/', LatestActionsByTeam(), name='team_feed'),
+    path("languages/<locale:locale>/", LatestActionsByLanguage(), name="lang_feed"),
+    path("teams/<locale:team_name>/", LatestActionsByTeam(), name="team_feed"),
 ]
diff --git a/languages/admin.py b/languages/admin.py
index d32b6cc3..a5809733 100644
--- a/languages/admin.py
+++ b/languages/admin.py
@@ -1,9 +1,10 @@
 from django.contrib import admin
+
 from languages.models import Language
 
 
 class LanguageAdmin(admin.ModelAdmin):
-    search_fields = ('name', 'locale')
+    search_fields = ("name", "locale")
 
 
 admin.site.register(Language, LanguageAdmin)
diff --git a/languages/management/commands/load-plurals.py b/languages/management/commands/load-plurals.py
index 9c66f70e..02e51275 100644
--- a/languages/management/commands/load-plurals.py
+++ b/languages/management/commands/load-plurals.py
@@ -1,4 +1,5 @@
 from django.core.management.base import BaseCommand
+
 from languages.models import Language
 
 # Shamelessly copied from http://translate.sourceforge.net/wiki/l10n/pluralforms (2009-05-25)
@@ -6,113 +7,113 @@ from languages.models import Language
 # pylint: disable=line-too-long
 # flake8: noqa
 plurals = {
-    'af': "nplurals=2; plural=(n != 1)",
-    'ak': "nplurals=2; plural=(n > 1)",
-    'am': "nplurals=2; plural=(n > 1)",
-    'ar': "nplurals=6; plural= n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && 
n%100<=99 ? 4 : 5;",
-    'arn': "nplurals=2; plural=(n > 1)",
-    'ay': "nplurals=1; plural=0",
-    'az': "nplurals=2; plural=(n != 1)",
-    'be': "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1 
: 2)",
-    'bg': "nplurals=2; plural=(n != 1)",
-    'bn': "nplurals=2; plural=(n != 1)",
-    'bo': "nplurals=1; plural=0",
-    'bs': "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1 
: 2)",
-    'ca': "nplurals=2; plural=(n != 1)",
-    'cs': "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n< =4) ? 1 : 2",
-    'csb': "nplurals=3; n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2",
-    'cy': "nplurals=4; plural= (n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3",
-    'da': "nplurals=2; plural=(n != 1)",
-    'de': "nplurals=2; plural=(n != 1)",
-    'dz': "nplurals=1; plural=0",
-    'el': "nplurals=2; plural=(n != 1)",
-    'en': "nplurals=2; plural=(n != 1)",
-    'eo': "nplurals=2; plural=(n != 1)",
-    'es': "nplurals=2; plural=(n != 1)",
-    'es_AR': "nplurals=2; plural=(n != 1)",
-    'et': "nplurals=2; plural=(n != 1)",
-    'eu': "nplurals=2; plural=(n != 1)",
-    'fa': "nplurals=1; plural=0",
-    'fi': "nplurals=2; plural=(n != 1)",
-    'fil': "nplurals=2; plural=n > 1",
-    'fo': "nplurals=2; plural=(n != 1)",
-    'fr': "nplurals=2; plural=(n > 1)",
-    'fur': "nplurals=2; plural=(n != 1)",
-    'fy': "nplurals=2; plural=(n != 1)",
-    'ga': "nplurals=5; plural=n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4",
-    'gl': "nplurals=2; plural=(n != 1)",
-    'gu': "nplurals=2; plural=(n != 1)",
-    'gun': "nplurals=2; plural = (n > 1)",
-    'ha': "nplurals=2; plural=(n != 1)",
-    'he': "nplurals=2; plural=(n != 1)",
-    'hi': "nplurals=2; plural=(n != 1)",
-    'hy': "nplurals=1; plural=0",
-    'hr': "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1 
: 2)",
-    'hu': "nplurals=1; plural=0",
-    'id': "nplurals=1; plural=0",
-    'is': "nplurals=2; plural=(n % 100 != 1 && n % 100 != 21 && n % 100 != 31 && n % 100 != 41 && n % 100 != 
51 && n % 100 != 61 && n % 100 != 71 && n % 100 != 81 && n % 100 != 91)",
-    'it': "nplurals=2; plural=(n != 1)",
-    'ja': "nplurals=1; plural=0",
-    'jv': "nplurals=2; plural=n!=0",
-    'ka': "nplurals=1; plural=0",
-    'km': "nplurals=1; plural=0",
-    'kn': "nplurals=2; plural=(n!=1)",
-    'ko': "nplurals=1; plural=0",
-    'ku': "nplurals=2; plural=(n!= 1)",
-    'kw': "nplurals=4; plural= (n==1) ? 0 : (n==2) ? 1 : (n == 3) ? 2 : 3",
-    'ky': "nplurals=1; plural=0",
-    'lb': "nplurals=2; plural=(n != 1)",
-    'ln': "nplurals=2; plural=n>1;",
-    'lo': "nplurals=1; plural=0",
-    'lt': "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2)",
-    'lv': "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2)",
-    'mg': "nplurals=2; plural=(n > 1)",
-    'mi': "nplurals=2; plural=(n > 1)",
-    'mk': "nplurals=2; plural= n==1 || n%10==1 ? 0 : 1",
-    'ml': "nplurals=2; plural=(n != 1)",
-    'mn': "nplurals=2; plural=(n != 1)",
-    'mr': "nplurals=2; plural=(n != 1)",
-    'ms': "nplurals=1; plural=0",
-    'mt': "nplurals=4; plural=(n==1 ? 0 : n==0 || ( n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20 ) ? 2 : 
3)",
-    'nah': "nplurals=2; plural=(n != 1)",
-    'nap': "nplurals=2; plural=(n != 1)",
-    'nb': "nplurals=2; plural=(n != 1)",
-    'ne': "nplurals=2; plural=(n != 1)",
-    'nl': "nplurals=2; plural=(n != 1)",
-    'nn': "nplurals=2; plural=(n != 1)",
-    'no': "nplurals=2; plural=(n != 1)",
-    'nso': "nplurals=2; plural=(n > 1)",
-    'or': "nplurals=2; plural=(n != 1)",
-    'ps': "nplurals=2; plural=(n != 1)",
-    'pa': "nplurals=2; plural=(n != 1)",
-    'pap': "nplurals=2; plural=(n != 1)",
-    'pl': "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1 : 2)",
-    'pms': "nplurals=2; plural=(n != 1)",
-    'pt': "nplurals=2; plural=(n != 1)",
-    'pt_BR': "nplurals=2; plural=(n > 1)",
-    'ro': "nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2);",
-    'ru': "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1 
: 2)",
-    'sco': "nplurals=2; plural=(n != 1)",
-    'sk': "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2",
-    'sl': "nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3)",
-    'so': "nplurals=2; plural=n != 1",
-    'sq': "nplurals=2; plural=(n != 1)",
-    'sr': "nplurals=4; plural=n==1? 3 : n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || 
n%100>=20) ? 1 : 2",
-    'su': "nplurals=1; plural=0",
-    'sv': "nplurals=2; plural=(n != 1)",
-    'ta': "nplurals=2; plural=(n != 1)",
-    'te': "nplurals=2; plural=(n != 1)",
-    'tg': "nplurals=2; plural=(n != 1)",
-    'ti': "nplurals=2; plural=n > 1",
-    'th': "nplurals=1; plural=0",
-    'tk': "nplurals=2; plural=(n != 1)",
-    'tr': "nplurals=1; plural=0",
-    'uk': "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1 
: 2)",
-    'ur': "nplurals=2; plural=(n != 1)",
-    'uz': "nplurals=1; plural=0;",
-    'vi': "nplurals=1; plural=0",
-    'wa': "nplurals=2; plural=(n > 1)",
-    'zh': "nplurals=1; plural=0",
+    "af": "nplurals=2; plural=(n != 1)",
+    "ak": "nplurals=2; plural=(n > 1)",
+    "am": "nplurals=2; plural=(n > 1)",
+    "ar": "nplurals=6; plural= n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && 
n%100<=99 ? 4 : 5;",
+    "arn": "nplurals=2; plural=(n > 1)",
+    "ay": "nplurals=1; plural=0",
+    "az": "nplurals=2; plural=(n != 1)",
+    "be": "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1 
: 2)",
+    "bg": "nplurals=2; plural=(n != 1)",
+    "bn": "nplurals=2; plural=(n != 1)",
+    "bo": "nplurals=1; plural=0",
+    "bs": "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1 
: 2)",
+    "ca": "nplurals=2; plural=(n != 1)",
+    "cs": "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n< =4) ? 1 : 2",
+    "csb": "nplurals=3; n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2",
+    "cy": "nplurals=4; plural= (n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3",
+    "da": "nplurals=2; plural=(n != 1)",
+    "de": "nplurals=2; plural=(n != 1)",
+    "dz": "nplurals=1; plural=0",
+    "el": "nplurals=2; plural=(n != 1)",
+    "en": "nplurals=2; plural=(n != 1)",
+    "eo": "nplurals=2; plural=(n != 1)",
+    "es": "nplurals=2; plural=(n != 1)",
+    "es_AR": "nplurals=2; plural=(n != 1)",
+    "et": "nplurals=2; plural=(n != 1)",
+    "eu": "nplurals=2; plural=(n != 1)",
+    "fa": "nplurals=1; plural=0",
+    "fi": "nplurals=2; plural=(n != 1)",
+    "fil": "nplurals=2; plural=n > 1",
+    "fo": "nplurals=2; plural=(n != 1)",
+    "fr": "nplurals=2; plural=(n > 1)",
+    "fur": "nplurals=2; plural=(n != 1)",
+    "fy": "nplurals=2; plural=(n != 1)",
+    "ga": "nplurals=5; plural=n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4",
+    "gl": "nplurals=2; plural=(n != 1)",
+    "gu": "nplurals=2; plural=(n != 1)",
+    "gun": "nplurals=2; plural = (n > 1)",
+    "ha": "nplurals=2; plural=(n != 1)",
+    "he": "nplurals=2; plural=(n != 1)",
+    "hi": "nplurals=2; plural=(n != 1)",
+    "hy": "nplurals=1; plural=0",
+    "hr": "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1 
: 2)",
+    "hu": "nplurals=1; plural=0",
+    "id": "nplurals=1; plural=0",
+    "is": "nplurals=2; plural=(n % 100 != 1 && n % 100 != 21 && n % 100 != 31 && n % 100 != 41 && n % 100 != 
51 && n % 100 != 61 && n % 100 != 71 && n % 100 != 81 && n % 100 != 91)",
+    "it": "nplurals=2; plural=(n != 1)",
+    "ja": "nplurals=1; plural=0",
+    "jv": "nplurals=2; plural=n!=0",
+    "ka": "nplurals=1; plural=0",
+    "km": "nplurals=1; plural=0",
+    "kn": "nplurals=2; plural=(n!=1)",
+    "ko": "nplurals=1; plural=0",
+    "ku": "nplurals=2; plural=(n!= 1)",
+    "kw": "nplurals=4; plural= (n==1) ? 0 : (n==2) ? 1 : (n == 3) ? 2 : 3",
+    "ky": "nplurals=1; plural=0",
+    "lb": "nplurals=2; plural=(n != 1)",
+    "ln": "nplurals=2; plural=n>1;",
+    "lo": "nplurals=1; plural=0",
+    "lt": "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2)",
+    "lv": "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2)",
+    "mg": "nplurals=2; plural=(n > 1)",
+    "mi": "nplurals=2; plural=(n > 1)",
+    "mk": "nplurals=2; plural= n==1 || n%10==1 ? 0 : 1",
+    "ml": "nplurals=2; plural=(n != 1)",
+    "mn": "nplurals=2; plural=(n != 1)",
+    "mr": "nplurals=2; plural=(n != 1)",
+    "ms": "nplurals=1; plural=0",
+    "mt": "nplurals=4; plural=(n==1 ? 0 : n==0 || ( n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20 ) ? 2 : 
3)",
+    "nah": "nplurals=2; plural=(n != 1)",
+    "nap": "nplurals=2; plural=(n != 1)",
+    "nb": "nplurals=2; plural=(n != 1)",
+    "ne": "nplurals=2; plural=(n != 1)",
+    "nl": "nplurals=2; plural=(n != 1)",
+    "nn": "nplurals=2; plural=(n != 1)",
+    "no": "nplurals=2; plural=(n != 1)",
+    "nso": "nplurals=2; plural=(n > 1)",
+    "or": "nplurals=2; plural=(n != 1)",
+    "ps": "nplurals=2; plural=(n != 1)",
+    "pa": "nplurals=2; plural=(n != 1)",
+    "pap": "nplurals=2; plural=(n != 1)",
+    "pl": "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1 : 2)",
+    "pms": "nplurals=2; plural=(n != 1)",
+    "pt": "nplurals=2; plural=(n != 1)",
+    "pt_BR": "nplurals=2; plural=(n > 1)",
+    "ro": "nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2);",
+    "ru": "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1 
: 2)",
+    "sco": "nplurals=2; plural=(n != 1)",
+    "sk": "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2",
+    "sl": "nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3)",
+    "so": "nplurals=2; plural=n != 1",
+    "sq": "nplurals=2; plural=(n != 1)",
+    "sr": "nplurals=4; plural=n==1? 3 : n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || 
n%100>=20) ? 1 : 2",
+    "su": "nplurals=1; plural=0",
+    "sv": "nplurals=2; plural=(n != 1)",
+    "ta": "nplurals=2; plural=(n != 1)",
+    "te": "nplurals=2; plural=(n != 1)",
+    "tg": "nplurals=2; plural=(n != 1)",
+    "ti": "nplurals=2; plural=n > 1",
+    "th": "nplurals=1; plural=0",
+    "tk": "nplurals=2; plural=(n != 1)",
+    "tr": "nplurals=1; plural=0",
+    "uk": "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1 
: 2)",
+    "ur": "nplurals=2; plural=(n != 1)",
+    "uz": "nplurals=1; plural=0;",
+    "vi": "nplurals=1; plural=0",
+    "wa": "nplurals=2; plural=(n > 1)",
+    "zh": "nplurals=1; plural=0",
     # 'zh means all districts and all variants of Chinese, such as zh_CN, zh_HK, zh_TW and so on.",
     # Note: In rare cases where plural form introduces difference in personal pronoun (such as her vs. they, 
we vs. I), the plural form is different:",
     # 'zh      Chinese         nplurals=2; plural=(n > 1)",
@@ -120,9 +121,8 @@ plurals = {
 
 
 class Command(BaseCommand):
-
     def handle(self, *args, **options):
-        """ Load known plural forms for existing languages """
+        """Load known plural forms for existing languages"""
 
         for locale in plurals:
             try:
diff --git a/languages/migrations/0001_initial.py b/languages/migrations/0001_initial.py
index 1c83fe24..19218ffa 100644
--- a/languages/migrations/0001_initial.py
+++ b/languages/migrations/0001_initial.py
@@ -1,25 +1,28 @@
-from django.db import models, migrations
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('teams', '__first__'),
+        ("teams", "__first__"),
     ]
 
     operations = [
         migrations.CreateModel(
-            name='Language',
+            name="Language",
             fields=[
-                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, 
primary_key=True)),
-                ('name', models.CharField(unique=True, max_length=50)),
-                ('locale', models.CharField(unique=True, max_length=15)),
-                ('plurals', models.CharField(max_length=200, blank=True)),
-                ('team', models.ForeignKey(default=None, blank=True, to='teams.Team', null=True, 
on_delete=models.SET_NULL)),
+                ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, 
primary_key=True)),
+                ("name", models.CharField(unique=True, max_length=50)),
+                ("locale", models.CharField(unique=True, max_length=15)),
+                ("plurals", models.CharField(max_length=200, blank=True)),
+                (
+                    "team",
+                    models.ForeignKey(default=None, blank=True, to="teams.Team", null=True, 
on_delete=models.SET_NULL),
+                ),
             ],
             options={
-                'ordering': ('name',),
-                'db_table': 'language',
+                "ordering": ("name",),
+                "db_table": "language",
             },
         ),
     ]
diff --git a/languages/models.py b/languages/models.py
index 6a6b12ee..323abf93 100644
--- a/languages/models.py
+++ b/languages/models.py
@@ -1,9 +1,10 @@
 from django.conf import settings
 from django.db import models
 from django.db.models import Q
-from django.utils.translation import gettext as _
 from django.urls import NoReverseMatch
-from teams.models import Team, FakeTeam
+from django.utils.translation import gettext as _
+
+from teams.models import FakeTeam, Team
 
 
 class Language(models.Model):
@@ -14,13 +15,13 @@ class Language(models.Model):
 
     # Mapping for code differences between GNOME and Django standards.
     lang_mapping = {
-        'zh_CN': 'zh_Hans',
-        'zh_TW': 'zh_Hant',
+        "zh_CN": "zh_Hans",
+        "zh_TW": "zh_Hant",
     }
 
     class Meta:
-        db_table = 'language'
-        ordering = ('name',)
+        db_table = "language"
+        ordering = ("name",)
 
     def __str__(self):
         return "%s (%s)" % (self.name, self.locale)
@@ -31,13 +32,13 @@ class Language(models.Model):
 
     @classmethod
     def get_language_from_ianacode(cls, ianacode):
-        """ Return a matching Language object corresponding to LANGUAGE_CODE (BCP47-formatted)
-            This is a sort of BCP47 to ISO639 conversion function """
+        """Return a matching Language object corresponding to LANGUAGE_CODE (BCP47-formatted)
+        This is a sort of BCP47 to ISO639 conversion function"""
         iana_splitted = ianacode.split("-", 1)
         lang_code = iana_splitted[0]
         iana_suffix = iana_splitted[1] if len(iana_splitted) > 1 else ""
-        iana_suffix = iana_suffix.replace('Latn', 'latin').replace('Cyrl', 'cyrillic')
-        lang_list = cls.objects.filter(Q(locale=lang_code) | Q(locale__regex=r'^%s[@_].+' % lang_code))
+        iana_suffix = iana_suffix.replace("Latn", "latin").replace("Cyrl", "cyrillic")
+        lang_list = cls.objects.filter(Q(locale=lang_code) | Q(locale__regex=r"^%s[@_].+" % lang_code))
         if len(lang_list) == 0:
             return None
         if len(lang_list) > 1:
@@ -58,7 +59,7 @@ class Language(models.Model):
         return self.locale
 
     def get_suffix(self):
-        splitted = self.locale.replace('@', '_').split('_')
+        splitted = self.locale.replace("@", "_").split("_")
         if len(splitted) > 1:
             return splitted[-1]
         return None
@@ -69,23 +70,23 @@ class Language(models.Model):
 
     def bugs_url_enter(self):
         return settings.ENTER_LANGUAGE_BUG_URL % {
-            'lang': self.locale,
+            "lang": self.locale,
         }
 
     def bugs_url_show(self):
         return settings.BROWSE_LANGUAGE_BUG_URL % {
-            'lang': self.locale,
+            "lang": self.locale,
         }
 
     def get_release_stats(self, archives=False):
         # FIXME Here be dragons
-        """ Get summary stats for all releases """
+        """Get summary stats for all releases"""
         from stats.models import Release
 
         if archives:
-            releases = Release.objects.all().filter(weight__lt=0).order_by('status', '-weight', '-name')
+            releases = Release.objects.all().filter(weight__lt=0).order_by("status", "-weight", "-name")
         else:
-            releases = Release.objects.all().filter(weight__gte=0).order_by('status', '-weight', '-name')
+            releases = Release.objects.all().filter(weight__gte=0).order_by("status", "-weight", "-name")
         stats = []
         for rel in releases:
             stats.append(rel.total_for_lang(self))
@@ -97,4 +98,4 @@ class Language(models.Model):
         try:
             return FakeTeam(self).get_absolute_url()
         except NoReverseMatch:
-            return ''
+            return ""
diff --git a/languages/tests.py b/languages/tests.py
index ecea8f50..2fb28205 100644
--- a/languages/tests.py
+++ b/languages/tests.py
@@ -1,25 +1,25 @@
-from django.urls import reverse
 from django.test import TestCase
+from django.urls import reverse
 
 from languages.models import Language
 
 
 class LanguageTestCase(TestCase):
-    fixtures = ['sample_data.json']
+    fixtures = ["sample_data.json"]
 
     def test_language_release_xml(self):
-        response = self.client.get(reverse('language_release_xml', args=['fr', 'gnome-3-8']))
+        response = self.client.get(reverse("language_release_xml", args=["fr", "gnome-3-8"]))
         self.assertContains(response, """<stats language="fr" release="gnome-3-8">""")
 
     def test_language_from_ianacode(self):
-        Language.objects.create(name='Belarussian', locale='be')
-        Language.objects.create(name='French (Belgium)', locale='fr_BE')
-        Language.objects.create(name='Chinese (Taiwan)', locale='zh_TW')
+        Language.objects.create(name="Belarussian", locale="be")
+        Language.objects.create(name="French (Belgium)", locale="fr_BE")
+        Language.objects.create(name="Chinese (Taiwan)", locale="zh_TW")
 
         func = Language.get_language_from_ianacode
-        self.assertEqual(func('fr-ch').locale, 'fr')
-        self.assertEqual(func('fr-be').locale, 'fr_BE')
-        self.assertEqual(func('be').locale, 'be')
-        self.assertEqual(func('be-latin-RU').locale, 'be')
-        self.assertEqual(func('zh-tw').locale, 'zh_TW')
-        self.assertEqual(func('xx'), None)
+        self.assertEqual(func("fr-ch").locale, "fr")
+        self.assertEqual(func("fr-be").locale, "fr_BE")
+        self.assertEqual(func("be").locale, "be")
+        self.assertEqual(func("be-latin-RU").locale, "be")
+        self.assertEqual(func("zh-tw").locale, "zh_TW")
+        self.assertEqual(func("xx"), None)
diff --git a/languages/urls.py b/languages/urls.py
index 6b61f7bd..2cb6d9d7 100644
--- a/languages/urls.py
+++ b/languages/urls.py
@@ -3,26 +3,16 @@ from django.urls import path
 from languages import views
 from teams import views as team_views
 
-
 urlpatterns = [
-    path('',
-         views.languages,
-         name='languages'),
-    path('<locale:locale>/all/<slug:dtype>/',
-         views.language_all,
-         name='language_all'),
-    path('<locale:locale>/rel-archives/',
-         views.release_archives,
-         name='language_release_archives'),
-    path('<locale:locale>/<name:release_name>/<slug:dtype>/',
-         views.language_release,
-         name='language_release'),
-    path('<locale:locale>/<name:release_name>/xml',
-         views.language_release_xml,
-         name='language_release_xml'),
-    path('<locale:locale>/<name:release_name>/<slug:dtype>.tar.gz',
-         views.language_release_tar,
-         name='language_release_tar'),
-    path('<locale:team_slug>/',
-         team_views.team),
+    path("", views.languages, name="languages"),
+    path("<locale:locale>/all/<slug:dtype>/", views.language_all, name="language_all"),
+    path("<locale:locale>/rel-archives/", views.release_archives, name="language_release_archives"),
+    path("<locale:locale>/<name:release_name>/<slug:dtype>/", views.language_release, 
name="language_release"),
+    path("<locale:locale>/<name:release_name>/xml", views.language_release_xml, name="language_release_xml"),
+    path(
+        "<locale:locale>/<name:release_name>/<slug:dtype>.tar.gz",
+        views.language_release_tar,
+        name="language_release_tar",
+    ),
+    path("<locale:team_slug>/", team_views.team),
 ]
diff --git a/languages/views.py b/languages/views.py
index 9999cca0..35c267e9 100644
--- a/languages/views.py
+++ b/languages/views.py
@@ -4,7 +4,7 @@ from datetime import date, datetime, timezone
 
 from django.conf import settings
 from django.http import HttpResponse, HttpResponseRedirect
-from django.shortcuts import render, get_object_or_404
+from django.shortcuts import get_object_or_404, render
 from django.utils.translation import gettext as _
 
 from common import utils
@@ -15,62 +15,64 @@ from stats.models import Release, Statistics
 def languages(request):
     languages = Language.objects.select_related("team").all()
     context = {
-        'pageSection': "languages",
-        'languages': utils.trans_sort_object_list(languages, 'name'),
+        "pageSection": "languages",
+        "languages": utils.trans_sort_object_list(languages, "name"),
     }
-    return render(request, 'languages/language_list.html', context)
+    return render(request, "languages/language_list.html", context)
 
 
 def language_all(request, locale, dtype):
     language = get_object_or_404(Language, locale=locale)
     stats = Statistics.get_lang_stats_by_type(language, dtype, release=None)
     context = {
-        'pageSection': "languages",
-        'language': language,
-        'stats_title': {
-            'ui': _("UI Translations"),
-            'ui-part': _("UI Translations (reduced)"),
-            'doc': _("Documentation")}.get(dtype),
-        'stats': stats,
-        'scope': dtype.endswith('-part') and 'part' or 'full',
+        "pageSection": "languages",
+        "language": language,
+        "stats_title": {
+            "ui": _("UI Translations"),
+            "ui-part": _("UI Translations (reduced)"),
+            "doc": _("Documentation"),
+        }.get(dtype),
+        "stats": stats,
+        "scope": dtype.endswith("-part") and "part" or "full",
     }
-    return render(request, 'languages/language_all_modules.html', context)
+    return render(request, "languages/language_all_modules.html", context)
 
 
 def release_archives(request, locale):
-    """ This view is used to display archive release stats through Ajax call
-        Only the HTML table is produced
+    """This view is used to display archive release stats through Ajax call
+    Only the HTML table is produced
     """
     language = get_object_or_404(Language, locale=locale)
     context = {
-        'lang': language,
-        'stats': language.get_release_stats(archives=True),
-        'show_all_modules_line': False,
+        "lang": language,
+        "stats": language.get_release_stats(archives=True),
+        "show_all_modules_line": False,
     }
-    return render(request, 'languages/language_release_summary.html', context)
+    return render(request, "languages/language_release_summary.html", context)
 
 
 def language_release(request, locale, release_name, dtype):
-    if locale == 'C':
+    if locale == "C":
         language = None
     else:
         language = get_object_or_404(Language, locale=locale)
     release = get_object_or_404(Release, name=release_name)
     stats = Statistics.get_lang_stats_by_type(language, dtype, release)
     context = {
-        'pageSection': "languages",
-        'language': language,
-        'language_name': language and language.get_name() or _("Original strings"),
-        'release': release,
-        'stats_title': {
-            'ui': _("UI Translations"),
-            'ui-part': _("UI Translations (reduced)"),
-            'doc': _("Documentation")}.get(dtype),
-        'stats': stats,
-        'dtype': dtype,
-        'scope': dtype.endswith('-part') and 'part' or 'full',
+        "pageSection": "languages",
+        "language": language,
+        "language_name": language and language.get_name() or _("Original strings"),
+        "release": release,
+        "stats_title": {
+            "ui": _("UI Translations"),
+            "ui-part": _("UI Translations (reduced)"),
+            "doc": _("Documentation"),
+        }.get(dtype),
+        "stats": stats,
+        "dtype": dtype,
+        "scope": dtype.endswith("-part") and "part" or "full",
     }
-    return render(request, 'languages/language_release.html', context)
+    return render(request, "languages/language_release.html", context)
 
 
 def language_release_tar(request, locale, release_name, dtype):
@@ -78,17 +80,16 @@ def language_release_tar(request, locale, release_name, dtype):
     language = get_object_or_404(Language, locale=locale)
     last_modif, file_list = release.get_lang_files(language, dtype)
 
-    tar_filename = '%s.%s.%s.%s.tar.gz' % (release.name, dtype, language.locale, date.today())
-    tar_directory = os.path.join(settings.POTDIR, 'tar')
+    tar_filename = "%s.%s.%s.%s.tar.gz" % (release.name, dtype, language.locale, date.today())
+    tar_directory = os.path.join(settings.POTDIR, "tar")
     if not os.access(tar_directory, os.R_OK):
         os.mkdir(tar_directory)
     tar_path = os.path.join(tar_directory, tar_filename)
-    if (
-        not os.access(tar_path, os.R_OK) or
-        last_modif > datetime.fromtimestamp(os.path.getmtime(tar_path), timezone.utc)
+    if not os.access(tar_path, os.R_OK) or last_modif > datetime.fromtimestamp(
+        os.path.getmtime(tar_path), timezone.utc
     ):
         # Create a new tar file
-        tar_file = tarfile.open(tar_path, 'w:gz')
+        tar_file = tarfile.open(tar_path, "w:gz")
         for f in file_list:
             tar_file.add(f, os.path.basename(f))
         tar_file.close()
@@ -97,31 +98,31 @@ def language_release_tar(request, locale, release_name, dtype):
 
 
 def language_release_xml(request, locale, release_name):
-    """ This view create the same XML output than the previous Damned-Lies, so as
-        apps which depend on it (like Vertimus) don't break.
-        This view may be suppressed when Vertimus will be integrated in D-L. """
+    """This view create the same XML output than the previous Damned-Lies, so as
+    apps which depend on it (like Vertimus) don't break.
+    This view may be suppressed when Vertimus will be integrated in D-L."""
     language = get_object_or_404(Language, locale=locale)
     release = get_object_or_404(Release, name=release_name)
     stats = release.get_lang_stats(language)
-    content = "<stats language=\"%s\" release=\"%s\">\n" % (locale, release_name)
-    for catname, categ in stats['ui']['categs'].items():
-        if catname != 'default':
-            content += "<category id=\"%s\">" % catname
+    content = '<stats language="%s" release="%s">\n' % (locale, release_name)
+    for catname, categ in stats["ui"]["categs"].items():
+        if catname != "default":
+            content += '<category id="%s">' % catname
         # totals for category
-        if catname in stats['doc']['categs']:
-            content += "<doctranslated>%s</doctranslated>" % stats['doc']['categs'][catname]['cattrans']
-            content += "<docfuzzy>%s</docfuzzy>" % stats['doc']['categs'][catname]['catfuzzy']
-            content += "<docuntranslated>%s</docuntranslated>" % 
stats['doc']['categs'][catname]['catuntrans']
-        content += "<translated>%s</translated>" % categ['cattrans']
-        content += "<fuzzy>%s</fuzzy>" % categ['catfuzzy']
-        content += "<untranslated>%s</untranslated>" % categ['catuntrans']
+        if catname in stats["doc"]["categs"]:
+            content += "<doctranslated>%s</doctranslated>" % stats["doc"]["categs"][catname]["cattrans"]
+            content += "<docfuzzy>%s</docfuzzy>" % stats["doc"]["categs"][catname]["catfuzzy"]
+            content += "<docuntranslated>%s</docuntranslated>" % 
stats["doc"]["categs"][catname]["catuntrans"]
+        content += "<translated>%s</translated>" % categ["cattrans"]
+        content += "<fuzzy>%s</fuzzy>" % categ["catfuzzy"]
+        content += "<untranslated>%s</untranslated>" % categ["catuntrans"]
         # Modules
-        for module, mod_stats in categ['modules'].items():
+        for module, mod_stats in categ["modules"].items():
             branch_name, domains = list(mod_stats.items())[0]
-            content += "<module id=\"%s\" branch=\"%s\">" % (module.name, branch_name)
+            content += '<module id="%s" branch="%s">' % (module.name, branch_name)
             # DOC domains
-            if catname in stats['doc']['categs'] and stats['doc']['categs'][catname]['modules']:
-                for docmod, data in stats['doc']['categs'][catname]['modules'].items():
+            if catname in stats["doc"]["categs"] and stats["doc"]["categs"][catname]["modules"]:
+                for docmod, data in stats["doc"]["categs"][catname]["modules"].items():
                     if docmod == module:
                         content += get_domain_stats(list(data.values())[0], "document")
             # UI stats
@@ -129,36 +130,36 @@ def language_release_xml(request, locale, release_name):
             content += "</module>"
         # Add modules who have no ui counterparts
         # FIXME: duplicated code fragment. Replace respectively 'dev-tools' and 'desktop' with the catname 
variable
-        if catname == 'dev-tools':
+        if catname == "dev-tools":
             try:
-                mod = [m for m in stats['doc']['categs']['dev-tools']['modules'] if m[0] == 
'gnome-devel-docs'][0][1]
-                content += "<module id=\"gnome-devel-docs\" branch=\"%s\">" % mod.keys()[0]
+                mod = [m for m in stats["doc"]["categs"]["dev-tools"]["modules"] if m[0] == 
"gnome-devel-docs"][0][1]
+                content += '<module id="gnome-devel-docs" branch="%s">' % mod.keys()[0]
                 content += get_domain_stats(mod.values()[0], "document")
                 content += "</module>"
             except Exception:  # FIXME: which exception is this catching?
                 pass
-        if catname == 'desktop':
+        if catname == "desktop":
             try:
-                mod = [m for m in stats['doc']['categs']['desktop']['modules'] if m[0] == 
'gnome-user-docs'][0][1]
-                content += "<module id=\"gnome-user-docs\" branch=\"%s\">" % mod.keys()[0]
+                mod = [m for m in stats["doc"]["categs"]["desktop"]["modules"] if m[0] == 
"gnome-user-docs"][0][1]
+                content += '<module id="gnome-user-docs" branch="%s">' % mod.keys()[0]
                 content += get_domain_stats(mod.values()[0], "document")
                 content += "</module>"
             except Exception:  # FIXME: which exception is this catching?
                 pass
 
-        if catname != 'default':
+        if catname != "default":
             content += "</category>"
     content += "</stats>"
-    return HttpResponse(content, content_type='text/xml')
+    return HttpResponse(content, content_type="text/xml")
 
 
 def get_domain_stats(mods, node_name):
-    """ Iterate module domains to get stats """
+    """Iterate module domains to get stats"""
     content = ""
     for dom_key, stat in mods:
-        if dom_key == ' fake':
+        if dom_key == " fake":
             continue
-        content += "<%s id=\"%s\">" % (node_name, stat.domain.name)
+        content += '<%s id="%s">' % (node_name, stat.domain.name)
         content += "<translated>%s</translated>" % stat.translated()
         content += "<fuzzy>%s</fuzzy>" % stat.fuzzy()
         content += "<untranslated>%s</untranslated>" % stat.untranslated()
@@ -170,8 +171,8 @@ def get_domain_stats(mods, node_name):
 
 # ********* Utility functions ******************
 def clean_tar_files():
-    """ Delete outdated tar.gz files generated by the language_release_tar view """
-    tar_directory = os.path.join(settings.POTDIR, 'tar')
+    """Delete outdated tar.gz files generated by the language_release_tar view"""
+    tar_directory = os.path.join(settings.POTDIR, "tar")
     if not os.path.exists(tar_directory):
         return
     for tar_file in os.listdir(tar_directory):
diff --git a/people/admin.py b/people/admin.py
index dcfee618..843c3cd9 100644
--- a/people/admin.py
+++ b/people/admin.py
@@ -1,16 +1,17 @@
 from django.contrib import admin
 from django.contrib.auth.admin import UserAdmin
 from django.contrib.auth.models import User
+
 from people.models import Person
 
 
 @admin.register(Person)
 class PersonAdmin(admin.ModelAdmin):
-    search_fields = ('username', 'first_name', 'last_name', 'email')
-    list_display = ('username', 'first_name', 'last_name', 'email', 'webpage_url')
+    search_fields = ("username", "first_name", "last_name", "email")
+    list_display = ("username", "first_name", "last_name", "email", "webpage_url")
 
 
-UserAdmin.list_display = ('username', 'email', 'last_name', 'first_name', 'is_active', 'last_login')
+UserAdmin.list_display = ("username", "email", "last_name", "first_name", "is_active", "last_login")
 
 admin.site.unregister(User)
 admin.site.register(User, UserAdmin)
diff --git a/people/forms.py b/people/forms.py
index ce6e475c..d682100c 100644
--- a/people/forms.py
+++ b/people/forms.py
@@ -3,17 +3,16 @@ import random
 from http.client import InvalidURL
 from urllib.request import urlopen
 
-from PIL import ImageFile
-
 from django import forms
 from django.conf import settings
-from django.contrib.auth.forms import AuthenticationForm
-from django.contrib.auth.forms import UsernameField
+from django.contrib.auth.forms import AuthenticationForm, UsernameField
 from django.contrib.auth.validators import UnicodeUsernameValidator
 from django.core.exceptions import ValidationError
 from django.urls import reverse
 from django.utils.encoding import force_bytes
-from django.utils.translation import gettext_lazy, gettext as _
+from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy
+from PIL import ImageFile
 
 from common.utils import send_mail
 from people.models import Person
@@ -23,48 +22,55 @@ username_validator = UnicodeUsernameValidator()
 
 
 class RegistrationForm(forms.Form):
-    """ Form for user registration """
-    username = UsernameField(max_length=30,
-                             label=gettext_lazy('Choose a username:'),
-                             validators=[username_validator],
-                             help_text=gettext_lazy('May contain only letters, numbers, and @/./+/-/_ 
characters.'),
-                             widget=forms.TextInput(attrs={'class': 'form-control'}))
-    email = forms.EmailField(widget=forms.TextInput(attrs={'class': 'form-control'}),
-                             label=gettext_lazy('Email:'))
-    password1 = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'form-control'}, 
render_value=False),
-                                label=gettext_lazy('Password:'), required=False, min_length=7,
-                                help_text=gettext_lazy('At least 7 characters'))
-    password2 = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'form-control'}, 
render_value=False),
-                                label=gettext_lazy('Confirm password:'), required=False)
+    """Form for user registration"""
+
+    username = UsernameField(
+        max_length=30,
+        label=gettext_lazy("Choose a username:"),
+        validators=[username_validator],
+        help_text=gettext_lazy("May contain only letters, numbers, and @/./+/-/_ characters."),
+        widget=forms.TextInput(attrs={"class": "form-control"}),
+    )
+    email = forms.EmailField(widget=forms.TextInput(attrs={"class": "form-control"}), 
label=gettext_lazy("Email:"))
+    password1 = forms.CharField(
+        widget=forms.PasswordInput(attrs={"class": "form-control"}, render_value=False),
+        label=gettext_lazy("Password:"),
+        required=False,
+        min_length=7,
+        help_text=gettext_lazy("At least 7 characters"),
+    )
+    password2 = forms.CharField(
+        widget=forms.PasswordInput(attrs={"class": "form-control"}, render_value=False),
+        label=gettext_lazy("Confirm password:"),
+        required=False,
+    )
 
     def clean_username(self):
-        """  Validate the username (correctness and uniqueness)"""
+        """Validate the username (correctness and uniqueness)"""
         try:
-            Person.objects.get(username__iexact=self.cleaned_data['username'])
+            Person.objects.get(username__iexact=self.cleaned_data["username"])
         except Person.DoesNotExist:
-            return self.cleaned_data['username']
-        raise ValidationError(_('This username is already taken. Please choose another.'))
+            return self.cleaned_data["username"]
+        raise ValidationError(_("This username is already taken. Please choose another."))
 
     def clean(self):
         cleaned_data = self.cleaned_data
-        password1 = cleaned_data.get('password1')
-        password2 = cleaned_data.get('password2')
+        password1 = cleaned_data.get("password1")
+        password2 = cleaned_data.get("password2")
         if not password1:
-            raise ValidationError(_('You must provide a password'))
+            raise ValidationError(_("You must provide a password"))
 
         if password1 and password1 != password2:
-            raise ValidationError(_('The passwords do not match'))
+            raise ValidationError(_("The passwords do not match"))
         return cleaned_data
 
     def save(self, request):
-        """ Create the user """
-        username = self.cleaned_data['username']
-        email = self.cleaned_data['email']
-        password = self.cleaned_data['password1']
+        """Create the user"""
+        username = self.cleaned_data["username"]
+        email = self.cleaned_data["email"]
+        password = self.cleaned_data["password1"]
 
-        new_user = Person.objects.create_user(
-            username=username, email=email, password=password
-        )
+        new_user = Person.objects.create_user(username=username, email=email, password=password)
         salt = hashlib.sha1(force_bytes(random.random())).hexdigest()[:5]
         activation_key = hashlib.sha1(force_bytes(salt + username)).hexdigest()
         new_user.activation_key = activation_key
@@ -72,21 +78,24 @@ class RegistrationForm(forms.Form):
         new_user.save()
         # Send activation email
         site_domain = settings.SITE_DOMAIN
-        activation_url = str(reverse("register_activation", kwargs={'key': activation_key}))
-        message = _(
-            "This is a confirmation that your registration on %s succeeded. To activate your account, "
-            "please click on the link below or copy and paste it in a browser."
-        ) % site_domain
+        activation_url = str(reverse("register_activation", kwargs={"key": activation_key}))
+        message = (
+            _(
+                "This is a confirmation that your registration on %s succeeded. To activate your account, "
+                "please click on the link below or copy and paste it in a browser."
+            )
+            % site_domain
+        )
         message += "\n\nhttps://%s%s\n\n"; % (site_domain, activation_url)
         message += _("Administrators of %s" % site_domain)
-        send_mail(_('Account activation'), message, to=[email])
+        send_mail(_("Account activation"), message, to=[email])
         return new_user
 
 
 class LoginForm(AuthenticationForm):
     def clean_username(self):
-        username = self.cleaned_data['username']
-        if '@' in username and not Person.objects.filter(username=username).exists():
+        username = self.cleaned_data["username"]
+        if "@" in username and not Person.objects.filter(username=username).exists():
             try:
                 username = Person.objects.filter(email=username).first().username
             except AttributeError:
@@ -97,18 +106,16 @@ class LoginForm(AuthenticationForm):
 class DetailForm(forms.ModelForm):
     class Meta:
         model = Person
-        fields = ('username', 'first_name', 'last_name', 'email', 'image', 'avatar_service',
-                  'webpage_url', 'irc_nick')
+        fields = ("username", "first_name", "last_name", "email", "image", "avatar_service", "webpage_url", 
"irc_nick")
 
     def clean_image(self):
-        url = self.cleaned_data['image']
+        url = self.cleaned_data["image"]
         if url:
             size = get_image_size(url)
             if size[0] > 100 or size[1] > 100:
                 raise ValidationError(
-                    _("Image too high or too wide (%(width)d×%(height)d, maximum is 100×100 pixels)") % {
-                        'width': size[0], 'height': size[1]
-                    }
+                    _("Image too high or too wide (%(width)d×%(height)d, maximum is 100×100 pixels)")
+                    % {"width": size[0], "height": size[1]}
                 )
         return url
 
@@ -122,12 +129,12 @@ class TeamJoinForm(forms.Form):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         # FIXME: exclude team to which user is already member
-        self.fields['teams'] = TeamChoiceField(queryset=Team.objects.all())
+        self.fields["teams"] = TeamChoiceField(queryset=Team.objects.all())
 
 
 def get_image_size(url):
-    """ Returns width and height (as tuple) of the image poited at by the url
-        Code partially copied from http://effbot.org/zone/pil-image-size.htm """
+    """Returns width and height (as tuple) of the image poited at by the url
+    Code partially copied from http://effbot.org/zone/pil-image-size.htm""";
     try:
         im_file = urlopen(url)
     except (IOError, InvalidURL, EOFError, ValueError):
diff --git a/people/migrations/0001_initial.py b/people/migrations/0001_initial.py
index 8eac1ab7..96053779 100644
--- a/people/migrations/0001_initial.py
+++ b/people/migrations/0001_initial.py
@@ -1,34 +1,64 @@
-from django.db import models, migrations
-from django.conf import settings
 import django.contrib.auth.models
+from django.conf import settings
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('auth', '0001_initial'),
+        ("auth", "0001_initial"),
     ]
 
     operations = [
         migrations.CreateModel(
-            name='Person',
+            name="Person",
             fields=[
-                ('user_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, 
serialize=False, to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
-                ('svn_account', models.SlugField(max_length=20, null=True, blank=True)),
-                ('image', models.URLField(help_text='URL to an image file (.jpg, .png, …) of an hackergotchi 
(max. 100×100 pixels)', null=True, verbose_name='Image', blank=True)),
-                ('use_gravatar', models.BooleanField(default=False, help_text='Display the image of your 
gravatar.com account')),
-                ('webpage_url', models.URLField(null=True, verbose_name='Web page', blank=True)),
-                ('irc_nick', models.SlugField(max_length=20, null=True, verbose_name='IRC nickname', 
blank=True)),
-                ('bugzilla_account', models.EmailField(help_text="This should be an email address, useful if 
not equal to “E-mail address” field", max_length=254, null=True, verbose_name='Bugzilla account', 
blank=True)),
-                ('activation_key', models.CharField(max_length=40, null=True, blank=True)),
+                (
+                    "user_ptr",
+                    models.OneToOneField(
+                        parent_link=True,
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        to=settings.AUTH_USER_MODEL,
+                        on_delete=models.CASCADE,
+                    ),
+                ),
+                ("svn_account", models.SlugField(max_length=20, null=True, blank=True)),
+                (
+                    "image",
+                    models.URLField(
+                        help_text="URL to an image file (.jpg, .png, …) of an hackergotchi (max. 100×100 
pixels)",
+                        null=True,
+                        verbose_name="Image",
+                        blank=True,
+                    ),
+                ),
+                (
+                    "use_gravatar",
+                    models.BooleanField(default=False, help_text="Display the image of your gravatar.com 
account"),
+                ),
+                ("webpage_url", models.URLField(null=True, verbose_name="Web page", blank=True)),
+                ("irc_nick", models.SlugField(max_length=20, null=True, verbose_name="IRC nickname", 
blank=True)),
+                (
+                    "bugzilla_account",
+                    models.EmailField(
+                        help_text="This should be an email address, useful if not equal to “E-mail address” 
field",
+                        max_length=254,
+                        null=True,
+                        verbose_name="Bugzilla account",
+                        blank=True,
+                    ),
+                ),
+                ("activation_key", models.CharField(max_length=40, null=True, blank=True)),
             ],
             options={
-                'ordering': ('username',),
-                'db_table': 'person',
+                "ordering": ("username",),
+                "db_table": "person",
             },
-            bases=('auth.user',),
+            bases=("auth.user",),
             managers=[
-                ('objects', django.contrib.auth.models.UserManager()),
+                ("objects", django.contrib.auth.models.UserManager()),
             ],
         ),
     ]
diff --git a/people/migrations/0002_set_use_gravatar_verbose_name.py 
b/people/migrations/0002_set_use_gravatar_verbose_name.py
index 1e8ee6be..417ae88d 100644
--- a/people/migrations/0002_set_use_gravatar_verbose_name.py
+++ b/people/migrations/0002_set_use_gravatar_verbose_name.py
@@ -4,13 +4,15 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('people', '0001_initial'),
+        ("people", "0001_initial"),
     ]
 
     operations = [
         migrations.AlterField(
-            model_name='person',
-            name='use_gravatar',
-            field=models.BooleanField(default=False, help_text='Display the image of your gravatar.com 
account', verbose_name='Use gravatar'),
+            model_name="person",
+            name="use_gravatar",
+            field=models.BooleanField(
+                default=False, help_text="Display the image of your gravatar.com account", verbose_name="Use 
gravatar"
+            ),
         ),
     ]
diff --git a/people/migrations/0003_person_avatar_service.py b/people/migrations/0003_person_avatar_service.py
index 0196e94d..52a49c92 100644
--- a/people/migrations/0003_person_avatar_service.py
+++ b/people/migrations/0003_person_avatar_service.py
@@ -5,13 +5,18 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('people', '0002_set_use_gravatar_verbose_name'),
+        ("people", "0002_set_use_gravatar_verbose_name"),
     ]
 
     operations = [
         migrations.AddField(
-            model_name='person',
-            name='avatar_service',
-            field=models.CharField(blank=True, choices=[('libravatar.org', 'libravatar.org'), 
('gravatar.com', 'gravatar.com')], max_length=50, verbose_name='Avatar provider'),
+            model_name="person",
+            name="avatar_service",
+            field=models.CharField(
+                blank=True,
+                choices=[("libravatar.org", "libravatar.org"), ("gravatar.com", "gravatar.com")],
+                max_length=50,
+                verbose_name="Avatar provider",
+            ),
         ),
     ]
diff --git a/people/migrations/0004_migrate_use_gravatar.py b/people/migrations/0004_migrate_use_gravatar.py
index 2ee056d7..f654d57a 100644
--- a/people/migrations/0004_migrate_use_gravatar.py
+++ b/people/migrations/0004_migrate_use_gravatar.py
@@ -3,14 +3,13 @@ from django.db import migrations
 
 def migrate_gravatar(apps, schema_editor):
     Person = apps.get_model("people", "Person")
-    Person.objects.filter(use_gravatar=True).update(avatar_service='gravatar.com')
-
+    Person.objects.filter(use_gravatar=True).update(avatar_service="gravatar.com")
 
 
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('people', '0003_person_avatar_service'),
+        ("people", "0003_person_avatar_service"),
     ]
 
     operations = [migrations.RunPython(migrate_gravatar)]
diff --git a/people/migrations/0005_remove_person_use_gravatar.py 
b/people/migrations/0005_remove_person_use_gravatar.py
index fac67856..40e9b354 100644
--- a/people/migrations/0005_remove_person_use_gravatar.py
+++ b/people/migrations/0005_remove_person_use_gravatar.py
@@ -5,12 +5,12 @@ from django.db import migrations
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('people', '0004_migrate_use_gravatar'),
+        ("people", "0004_migrate_use_gravatar"),
     ]
 
     operations = [
         migrations.RemoveField(
-            model_name='person',
-            name='use_gravatar',
+            model_name="person",
+            name="use_gravatar",
         ),
     ]
diff --git a/people/migrations/0006_remove_person_bugzilla_account.py 
b/people/migrations/0006_remove_person_bugzilla_account.py
index a469c8fa..6255af9b 100644
--- a/people/migrations/0006_remove_person_bugzilla_account.py
+++ b/people/migrations/0006_remove_person_bugzilla_account.py
@@ -4,12 +4,12 @@ from django.db import migrations
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('people', '0005_remove_person_use_gravatar'),
+        ("people", "0005_remove_person_use_gravatar"),
     ]
 
     operations = [
         migrations.RemoveField(
-            model_name='person',
-            name='bugzilla_account',
+            model_name="person",
+            name="bugzilla_account",
         ),
     ]
diff --git a/people/migrations/0007_person_auth_token.py b/people/migrations/0007_person_auth_token.py
index e90af768..2fde7241 100644
--- a/people/migrations/0007_person_auth_token.py
+++ b/people/migrations/0007_person_auth_token.py
@@ -4,13 +4,13 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('people', '0006_remove_person_bugzilla_account'),
+        ("people", "0006_remove_person_bugzilla_account"),
     ]
 
     operations = [
         migrations.AddField(
-            model_name='person',
-            name='auth_token',
-            field=models.CharField(blank=True, max_length=40, verbose_name='Authentication Token'),
+            model_name="person",
+            name="auth_token",
+            field=models.CharField(blank=True, max_length=40, verbose_name="Authentication Token"),
         ),
     ]
diff --git a/people/models.py b/people/models.py
index d72414dc..3a733bb3 100644
--- a/people/models.py
+++ b/people/models.py
@@ -13,32 +13,33 @@ from django.utils.safestring import mark_safe
 from django.utils.translation import gettext_lazy as _
 
 AVATAR_SERVICES = {
-    'gravatar.com': 'https://secure.gravatar.com/avatar/{hash}.jpg?{qs}',
+    "gravatar.com": "https://secure.gravatar.com/avatar/{hash}.jpg?{qs}";,
     # See https://wiki.libravatar.org/api/
-    'libravatar.org': 'https://seccdn.libravatar.org/avatar/{hash}?{qs}',
+    "libravatar.org": "https://seccdn.libravatar.org/avatar/{hash}?{qs}";,
 }
 
 
 def obfuscate_email(email):
     if email:
         # Do not replace the 1st dot in "First M. Name <name dom com>"
-        email = re.sub(r'(\w)\.(\w)', r'\1 dot \2', email)
-        return mark_safe(escape(email.replace('@', ' at ')).replace(' ', '&nbsp;'))
-    return ''
+        email = re.sub(r"(\w)\.(\w)", r"\1 dot \2", email)
+        return mark_safe(escape(email.replace("@", " at ")).replace(" ", "&nbsp;"))
+    return ""
 
 
 class Person(User):
-    """ The User class of D-L. """
+    """The User class of D-L."""
 
     auth_token = models.CharField(_("Authentication Token"), max_length=40, blank=True)
     svn_account = models.SlugField(max_length=20, null=True, blank=True)
     image = models.URLField(
-        _("Image"), null=True, blank=True,
-        help_text=_("URL to an image file (.jpg, .png, …) of an hackergotchi (max. 100×100 pixels)")
+        _("Image"),
+        null=True,
+        blank=True,
+        help_text=_("URL to an image file (.jpg, .png, …) of an hackergotchi (max. 100×100 pixels)"),
     )
     avatar_service = models.CharField(
-        _("Avatar provider"), max_length=50, blank=True,
-        choices=((name, name) for name in AVATAR_SERVICES)
+        _("Avatar provider"), max_length=50, blank=True, choices=((name, name) for name in AVATAR_SERVICES)
     )
     webpage_url = models.URLField(_("Web page"), null=True, blank=True)
     irc_nick = models.SlugField(_("IRC nickname"), max_length=20, null=True, blank=True)
@@ -48,38 +49,37 @@ class Person(User):
     objects = UserManager()
 
     class Meta:
-        db_table = 'person'
-        ordering = ('username',)
+        db_table = "person"
+        ordering = ("username",)
 
     @classmethod
     def clean_unactivated_accounts(cls):
         accounts = cls.objects.filter(
-            activation_key__isnull=False,
-            date_joined__lt=(timezone.now() - timedelta(days=10))
-        ).exclude(activation_key='')
+            activation_key__isnull=False, date_joined__lt=(timezone.now() - timedelta(days=10))
+        ).exclude(activation_key="")
         for account in accounts:
             account.delete()
 
     @classmethod
     def clean_obsolete_accounts(cls):
-        """ Remove accounts that:
-            - last login is more than 2 years
-            - is not coordinator
-            - is not module maintainer
-            - has no reserved module
+        """Remove accounts that:
+        - last login is more than 2 years
+        - is not coordinator
+        - is not module maintainer
+        - has no reserved module
         """
-        accounts = cls.objects.annotate(
-            num_modules=models.Count('maintains_modules')
-        ).annotate(
-            num_states=models.Count('state')
-        ).filter(
-            models.Q(last_login__lt=(timezone.now() - timedelta(days=730))) |
-            models.Q(last_login__isnull=True, date_joined__lt=(timezone.now() - timedelta(days=180)))
-        ).exclude(
-            role__role='coordinator'
-        ).exclude(
-            num_modules__gt=0
-        ).exclude(num_states__gt=0).order_by()
+        accounts = (
+            cls.objects.annotate(num_modules=models.Count("maintains_modules"))
+            .annotate(num_states=models.Count("state"))
+            .filter(
+                models.Q(last_login__lt=(timezone.now() - timedelta(days=730)))
+                | models.Q(last_login__isnull=True, date_joined__lt=(timezone.now() - timedelta(days=180)))
+            )
+            .exclude(role__role="coordinator")
+            .exclude(num_modules__gt=0)
+            .exclude(num_states__gt=0)
+            .order_by()
+        )
         for account in accounts:
             account.delete()
 
@@ -91,7 +91,7 @@ class Person(User):
             return user.person
         except Person.DoesNotExist:
             _dict = user.__dict__.copy()
-            del _dict['_state']
+            del _dict["_state"]
             user.person = Person(**_dict)
             user.person.save()
             return user.person
@@ -101,7 +101,7 @@ class Person(User):
         if not val:
             return None
         try:
-            return Person.objects.filter(**{key: val}).order_by('-last_login')[0]
+            return Person.objects.filter(**{key: val}).order_by("-last_login")[0]
         except IndexError:
             return None
 
@@ -136,12 +136,13 @@ class Person(User):
         return "%s <%s>" % (self.name, self.email)
 
     def get_absolute_url(self):
-        return reverse('person_detail_username', args=[self.username])
+        return reverse("person_detail_username", args=[self.username])
 
     def coordinates_teams(self):
         # Class imported here to avoid cyclic import
         # pylint: disable=import-outside-toplevel
         from teams.models import Team
+
         return Team.objects.filter(role__person__id=self.id).all()
 
     def is_coordinator(self, team):
@@ -149,24 +150,24 @@ class Person(User):
         If team is a Team instance, tell if current Person is coordinator of that team.
         If team = 'any', tell if current Person is coordinator of at least one team.
         """
-        if team == 'any':
-            return self.role_set.filter(role='coordinator').exists()
+        if team == "any":
+            return self.role_set.filter(role="coordinator").exists()
         try:
-            self.role_set.get(team__id=team.id, role='coordinator')
+            self.role_set.get(team__id=team.id, role="coordinator")
             return True
         except (ObjectDoesNotExist, AttributeError):
             return False
 
     def is_committer(self, team):
         try:
-            self.role_set.get(team__id=team.id, role__in=['committer', 'coordinator'])
+            self.role_set.get(team__id=team.id, role__in=["committer", "coordinator"])
             return True
         except (ObjectDoesNotExist, AttributeError):
             return False
 
     def is_reviewer(self, team):
         try:
-            self.role_set.get(team__id=team.id, role__in=['reviewer', 'committer', 'coordinator'])
+            self.role_set.get(team__id=team.id, role__in=["reviewer", "committer", "coordinator"])
             return True
         except (ObjectDoesNotExist, AttributeError):
             return False
@@ -182,7 +183,7 @@ class Person(User):
         return module in self.maintains_modules.all()
 
     def get_languages(self):
-        all_teams = [role.team for role in self.role_set.select_related('team')]
+        all_teams = [role.team for role in self.role_set.select_related("team")]
         all_languages = []
         for team in all_teams:
             all_languages.extend(team.get_languages())
diff --git a/people/templatetags/people.py b/people/templatetags/people.py
index 820be09c..02a3a8d1 100644
--- a/people/templatetags/people.py
+++ b/people/templatetags/people.py
@@ -17,10 +17,7 @@ def people_list(lst):
     Format a people list, with clickable person name.
     """
     # Translators: string used as separator in person list
-    return format_html_join(
-        _(", "), '<a href="{}">{}</a>',
-        ((pers.get_absolute_url(), pers.name) for pers in lst)
-    )
+    return format_html_join(_(", "), '<a href="{}">{}</a>', ((pers.get_absolute_url(), pers.name) for pers 
in lst))
 
 
 @register.filter
@@ -31,7 +28,7 @@ def people_image(person):
         url = AVATAR_SERVICES[person.avatar_service].format(
             hash=digest,
             # Size, default image, rating
-            qs=urlencode([('s', '80'), ('d', 'identicon'), ('r', 'g')])
+            qs=urlencode([("s", "80"), ("d", "identicon"), ("r", "g")]),
         )
         tag = format_html(
             '<img class="img-circle" src="{url}" alt="{alt}" crossorigin="anonymous">', url=url, 
alt=_("avatar icon")
@@ -39,10 +36,10 @@ def people_image(person):
     elif person and person.image:
         tag = format_html(
             '<img class="img-circle" src="{}" alt="{}" onerror="this.onerror = null; this.src=\'{}\'">',
-            person.image, person.name, nobody
+            person.image,
+            person.name,
+            nobody,
         )
     else:
-        tag = format_html(
-            '<img class="img-circle" src="{url}" alt="{alt}">', url=nobody, alt=_("generic person icon")
-        )
+        tag = format_html('<img class="img-circle" src="{url}" alt="{alt}">', url=nobody, alt=_("generic 
person icon"))
     return tag
diff --git a/people/tests.py b/people/tests.py
index bcf56959..0b54dcd3 100644
--- a/people/tests.py
+++ b/people/tests.py
@@ -5,7 +5,7 @@ from django.contrib import auth
 from django.contrib.staticfiles.finders import find
 from django.core import mail
 from django.core.exceptions import ValidationError
-from django.test import TestCase, Client
+from django.test import Client, TestCase
 from django.urls import reverse
 from django.utils import timezone
 from django.utils.safestring import SafeData
@@ -17,172 +17,156 @@ from people.models import Person, obfuscate_email
 
 
 class PeopleTestCase(TestCase):
-
-    def _create_person(self, seq='', **kwargs):
-        pn = Person(
-            first_name='John', last_name='Nothing',
-            email='jn%s devnull com' % seq, username='jn%s' % seq
-        )
+    def _create_person(self, seq="", **kwargs):
+        pn = Person(first_name="John", last_name="Nothing", email="jn%s devnull com" % seq, username="jn%s" 
% seq)
         for key, arg in kwargs.items():
             setattr(pn, key, arg)
-        pn.set_password('password')
+        pn.set_password("password")
         pn.save()
         return pn
 
     def test_register(self):
-        username = 'tèst-01'
+        username = "tèst-01"
         response = self.client.post(
-            reverse('register'),
-            {'username': username, 'password1': '1234567',
-             'password2': '1234567', 'email': 'test01 example org'}
+            reverse("register"),
+            {"username": username, "password1": "1234567", "password2": "1234567", "email": "test01 example 
org"},
         )
-        self.assertRedirects(response, reverse('register_success'))
+        self.assertRedirects(response, reverse("register_success"))
         self.assertEqual(Person.objects.filter(username=username).count(), 1)
         self.assertEqual(len(mail.outbox), 1)
-        self.assertIn(_('Account activation'), mail.outbox[0].subject)
+        self.assertIn(_("Account activation"), mail.outbox[0].subject)
 
     def test_activate_account(self):
         # Testing if is_active is False by default
         response = self.client.post(
-            reverse('register'),
-            {'username': 'newuser', 'email': 'newuser example org', 'password1': 'blah012', 'password2': 
'blah012'}
+            reverse("register"),
+            {"username": "newuser", "email": "newuser example org", "password1": "blah012", "password2": 
"blah012"},
         )
 
-        self.newu = Person.objects.get(username='newuser')
+        self.newu = Person.objects.get(username="newuser")
         self.assertFalse(self.newu.is_active)
 
         # Testing with a invalid activation key
-        response = self.client.get('/register/activate/a_invalid_key')
-        self.assertContains(response, 'Sorry, the key you provided is not valid.')
+        response = self.client.get("/register/activate/a_invalid_key")
+        self.assertContains(response, "Sorry, the key you provided is not valid.")
 
-        response = self.client.get('/register/activate/%s' % self.newu.activation_key, follow=True)
-        self.assertContains(response, 'Your account has been activated.')
+        response = self.client.get("/register/activate/%s" % self.newu.activation_key, follow=True)
+        self.assertContains(response, "Your account has been activated.")
 
-        self.newu = Person.objects.get(username='newuser')
+        self.newu = Person.objects.get(username="newuser")
         self.assertTrue(self.newu.is_active)
 
     def test_login_message(self):
         self.pn = self._create_person()
-        response = self.client.get(reverse('login'), HTTP_REFERER='http://foo/bar')
-        self.assertContains(
-            response,
-            '<input type="hidden" name="referer" value="http://foo/bar";>',
-            html=True
-        )
-        response = self.client.post(
-            reverse('login'), data={'username': 'jn', 'password': 'password'}, follow=True
-        )
-        self.assertContains(
-            response,
-            'You have not joined any translation team yet. '
-            'You can do it from <a href="/users/team_join/">your profile</a>.'
-        )
-        response = self.client.get(reverse('login'))
+        response = self.client.get(reverse("login"), HTTP_REFERER="http://foo/bar";)
+        self.assertContains(response, '<input type="hidden" name="referer" value="http://foo/bar";>', 
html=True)
+        response = self.client.post(reverse("login"), data={"username": "jn", "password": "password"}, 
follow=True)
         self.assertContains(
             response,
-            'You are already logged in as jn.'
+            "You have not joined any translation team yet. "
+            'You can do it from <a href="/users/team_join/">your profile</a>.',
         )
+        response = self.client.get(reverse("login"))
+        self.assertContains(response, "You are already logged in as jn.")
         self.assertNotContains(response, 'id="login-form"')
 
     def test_login_by_email(self):
         self.pn = self._create_person()
-        self.client.post(
-            reverse('login'), data={'username': 'notexist devnull com', 'password': 'password'}
-        )
+        self.client.post(reverse("login"), data={"username": "notexist devnull com", "password": "password"})
         user = auth.get_user(self.client)
         self.assertFalse(user.is_authenticated)
-        self.client.post(
-            reverse('login'), data={'username': 'jn devnull com', 'password': 'password'}
-        )
+        self.client.post(reverse("login"), data={"username": "jn devnull com", "password": "password"})
         user = auth.get_user(self.client)
         self.assertTrue(user.is_authenticated)
 
     def test_person_list(self):
         self.pn = self._create_person()
-        response = self.client.get(reverse('people'))
+        response = self.client.get(reverse("people"))
         self.assertContains(response, "GNOME is being developed by following people:")
         self.assertContains(response, "John Nothing")
 
     def test_login_link(self):
         pn = self._create_person()
         self.client.force_login(pn)
-        response = self.client.get(reverse('home'))
+        response = self.client.get(reverse("home"))
         self.assertContains(
             response,
             '<a aria-expanded="false" aria-haspopup="true" role="button" data-toggle="dropdown" 
class="dropdown-toggle'
             ' hidden-xs avatar" href="%s"><img class="img-circle" alt="generic person icon"'
-            ' src="/static/img/nobody.png"></a>' % (reverse('person_detail_username', args=[pn.username])),
-            html=True
+            ' src="/static/img/nobody.png"></a>' % (reverse("person_detail_username", args=[pn.username])),
+            html=True,
         )
 
     def test_edit_details(self):
         pn = self._create_person()
         self.client.force_login(pn)
-        response = self.client.get(reverse('person_detail_change'))
+        response = self.client.get(reverse("person_detail_change"))
         self.assertContains(response, "First name")
         post_data = {
-            'username': 'jn2', 'first_name': "Johnny", 'last_name': "Nothing",
-            'email': 'test02 example org',
-            'image': '', 'webpage_url': "http://www.example.org/";
+            "username": "jn2",
+            "first_name": "Johnny",
+            "last_name": "Nothing",
+            "email": "test02 example org",
+            "image": "",
+            "webpage_url": "http://www.example.org/";,
         }
-        response = self.client.post(reverse('person_detail_change'), post_data)
+        response = self.client.post(reverse("person_detail_change"), post_data)
         pn.refresh_from_db()
-        self.assertEqual(pn.username, 'jn2')
-        self.assertEqual(pn.email, 'test02 example org')
+        self.assertEqual(pn.username, "jn2")
+        self.assertEqual(pn.email, "test02 example org")
         # bad image url
-        post_data['image'] = "http://http://www.example.org/image.jpg";
+        post_data["image"] = "http://http://www.example.org/image.jpg";
         form = forms.DetailForm(post_data, instance=pn)
         self.assertFalse(form.is_valid())
-        self.assertTrue('image' in form.errors)
+        self.assertTrue("image" in form.errors)
 
     def test_create_token(self):
         pn = self._create_person()
-        self.assertEqual(pn.auth_token, '')
+        self.assertEqual(pn.auth_token, "")
         self.client.force_login(pn)
-        response = self.client.post(reverse('person_create_token'))
-        self.assertRedirects(response, reverse('person_detail_username', args=[pn.username]))
+        response = self.client.post(reverse("person_create_token"))
+        self.assertRedirects(response, reverse("person_detail_username", args=[pn.username]))
         pn.refresh_from_db()
-        self.assertNotEqual(pn.auth_token, '')
+        self.assertNotEqual(pn.auth_token, "")
 
     def test_delete_token(self):
-        pn = self._create_person(auth_token='askhdaksudakckascjbaskdaiwue')
+        pn = self._create_person(auth_token="askhdaksudakckascjbaskdaiwue")
         self.client.force_login(pn)
-        response = self.client.post(reverse('person_delete_token'))
-        self.assertRedirects(response, reverse('person_detail_username', args=[pn.username]))
+        response = self.client.post(reverse("person_delete_token"))
+        self.assertRedirects(response, reverse("person_detail_username", args=[pn.username]))
         pn.refresh_from_db()
-        self.assertEqual(pn.auth_token, '')
+        self.assertEqual(pn.auth_token, "")
 
     def test_token_authentication(self):
-        pn = self._create_person(auth_token='askhdaksudakckascjbaskdaiwue')
-        response = self.client.get(
-            reverse('home'), HTTP_AUTHENTICATION='Bearer askhdaksudakckascjbaskdaiwue'
-        )
-        self.assertFalse(response.context['user'].is_anonymous)
-        self.assertEqual(response.context['user'], pn)
+        pn = self._create_person(auth_token="askhdaksudakckascjbaskdaiwue")
+        response = self.client.get(reverse("home"), HTTP_AUTHENTICATION="Bearer 
askhdaksudakckascjbaskdaiwue")
+        self.assertFalse(response.context["user"].is_anonymous)
+        self.assertEqual(response.context["user"], pn)
 
     def test_obsolete_accounts(self):
-        from teams.models import Team, Role
-        from stats.models import Module, Branch, Domain
         from languages.models import Language
+        from stats.models import Branch, Domain, Module
+        from teams.models import Role, Team
         from vertimus.models import StateTranslating
+
         module = Module.objects.create(name="gnome-hello")
         Branch.checkout_on_creation = False
-        branch = Branch(name='gnome-2-24', module=module)
+        branch = Branch(name="gnome-2-24", module=module)
         branch.save(update_statistics=False)
-        domain = Domain.objects.create(module=module, name='po', layout='po/{lang}.po')
-        team = Team.objects.create(name='fr', description='French', mailing_list='french_ml example org')
-        lang = Language.objects.create(name='French', locale='fr', team=team)
+        domain = Domain.objects.create(module=module, name="po", layout="po/{lang}.po")
+        team = Team.objects.create(name="fr", description="French", mailing_list="french_ml example org")
+        lang = Language.objects.create(name="French", locale="fr", team=team)
 
         # Create Users
-        p1 = self._create_person(seq='1', last_login=timezone.now() - timedelta(days=700))
-        p2 = self._create_person(seq='2', last_login=timezone.now() - timedelta(days=800))
-        Role.objects.create(team=team, person=p2, role='coordinator')
-        p3 = self._create_person(seq='3', last_login=timezone.now() - timedelta(days=800))
+        p1 = self._create_person(seq="1", last_login=timezone.now() - timedelta(days=700))
+        p2 = self._create_person(seq="2", last_login=timezone.now() - timedelta(days=800))
+        Role.objects.create(team=team, person=p2, role="coordinator")
+        p3 = self._create_person(seq="3", last_login=timezone.now() - timedelta(days=800))
         module.maintainers.add(p3)
-        p4 = self._create_person(seq='4', last_login=timezone.now() - timedelta(days=800))
+        p4 = self._create_person(seq="4", last_login=timezone.now() - timedelta(days=800))
         StateTranslating.objects.create(branch=branch, domain=domain, language=lang, person=p4)
-        self._create_person(seq='5', last_login=timezone.now() - timedelta(days=800))
-        self._create_person(seq='6', date_joined=timezone.now() - timedelta(days=200), last_login=None)
+        self._create_person(seq="5", last_login=timezone.now() - timedelta(days=800))
+        self._create_person(seq="6", date_joined=timezone.now() - timedelta(days=200), last_login=None)
         # Test only p5/p6 should be deleted
         self.assertEqual(Person.objects.all().count(), 6)
         Person.clean_obsolete_accounts()
@@ -190,20 +174,19 @@ class PeopleTestCase(TestCase):
         self.assertEqual(set(Person.objects.all()), set([p1, p2, p3, p4]))
 
     def test_obfuscate_email(self):
-        self.assertEqual(obfuscate_email(''), '')
+        self.assertEqual(obfuscate_email(""), "")
         self.assertEqual(
-            obfuscate_email('me company domain org'),
-            'me&nbsp;dot&nbsp;company&nbsp;at&nbsp;domain&nbsp;dot&nbsp;org'
+            obfuscate_email("me company domain org"), 
"me&nbsp;dot&nbsp;company&nbsp;at&nbsp;domain&nbsp;dot&nbsp;org"
         )
-        self.assertIsInstance(obfuscate_email('me company domain org'), SafeData)
+        self.assertIsInstance(obfuscate_email("me company domain org"), SafeData)
         self.assertEqual(
-            obfuscate_email('George P. McLain <george domain org'),
-            'George&nbsp;P.&nbsp;McLain&nbsp;&lt;george&nbsp;at&nbsp;domain&nbsp;dot&nbsp;org'
+            obfuscate_email("George P. McLain <george domain org"),
+            "George&nbsp;P.&nbsp;McLain&nbsp;&lt;george&nbsp;at&nbsp;domain&nbsp;dot&nbsp;org",
         )
         self.assertEqual(
-            obfuscate_email('Me <me company domain org>\nYou <some-address example com>'),
-            'Me&nbsp;&lt;me&nbsp;dot&nbsp;company&nbsp;at&nbsp;domain&nbsp;dot&nbsp;org&gt;\n'
-            'You&nbsp;&lt;some-address&nbsp;at&nbsp;example&nbsp;dot&nbsp;com&gt;'
+            obfuscate_email("Me <me company domain org>\nYou <some-address example com>"),
+            "Me&nbsp;&lt;me&nbsp;dot&nbsp;company&nbsp;at&nbsp;domain&nbsp;dot&nbsp;org&gt;\n"
+            "You&nbsp;&lt;some-address&nbsp;at&nbsp;example&nbsp;dot&nbsp;com&gt;",
         )
 
     def test_people_image_tag(self):
@@ -211,46 +194,43 @@ class PeopleTestCase(TestCase):
         Test the people_image template tag.
         """
         from people.templatetags import people
+
         pn = self._create_person()
         self.assertHTMLEqual(
-            people.people_image(pn),
-            '<img class="img-circle" src="/static/img/nobody.png" alt="generic person icon">'
+            people.people_image(pn), '<img class="img-circle" src="/static/img/nobody.png" alt="generic 
person icon">'
         )
-        pn.image = 'http://www.example.org/my_image.png'
+        pn.image = "http://www.example.org/my_image.png";
         self.assertHTMLEqual(
             people.people_image(pn),
             '<img class="img-circle" alt="John Nothing" onerror="this.onerror = null; '
-            'this.src=\'/static/img/nobody.png\'" src="http://www.example.org/my_image.png"; />'
+            'this.src=\'/static/img/nobody.png\'" src="http://www.example.org/my_image.png"; />',
         )
         pn.last_name = "<script>Some XSS content</script>"
         self.assertIn("&lt;script&gt;Some XSS content&lt;/script&gt;", people.people_image(pn))
         self.assertHTMLEqual(
             people.people_image(pn),
             '<img class="img-circle" alt="John &lt;script&gt;Some XSS content&lt;/script&gt;" '
-            'onerror="this.onerror = null; this.src=\'/static/img/nobody.png\'" '
-            'src="http://www.example.org/my_image.png"; />'
+            "onerror=\"this.onerror = null; this.src='/static/img/nobody.png'\" "
+            'src="http://www.example.org/my_image.png"; />',
         )
-        pn.avatar_service = 'gravatar.com'
+        pn.avatar_service = "gravatar.com"
         self.assertHTMLEqual(
             people.people_image(pn),
             '<img class="img-circle" alt="avatar icon" crossorigin="anonymous" '
-            
'src="https://secure.gravatar.com/avatar/618b8b6c1c973c780ec218242c49cbe7.jpg?s=80&d=identicon&r=g";>'
+            
'src="https://secure.gravatar.com/avatar/618b8b6c1c973c780ec218242c49cbe7.jpg?s=80&d=identicon&r=g";>',
         )
-        pn.avatar_service = 'libravatar.org'
+        pn.avatar_service = "libravatar.org"
         self.assertHTMLEqual(
             people.people_image(pn),
             '<img class="img-circle" alt="avatar icon"  crossorigin="anonymous" '
-            
'src="https://seccdn.libravatar.org/avatar/618b8b6c1c973c780ec218242c49cbe7?s=80&d=identicon&r=g";>'
+            
'src="https://seccdn.libravatar.org/avatar/618b8b6c1c973c780ec218242c49cbe7?s=80&d=identicon&r=g";>',
         )
 
-    @mock.patch('people.forms.urlopen')
+    @mock.patch("people.forms.urlopen")
     def test_get_image_size(self, m_urlopen):
-        with open(find('img/nobody.png'), mode='rb') as fh:
+        with open(find("img/nobody.png"), mode="rb") as fh:
             m_urlopen.return_value = fh
-            self.assertEqual(
-                forms.get_image_size("https://l10n.gnome.org/static/img/nobody.png";),
-                (48, 48)
-            )
+            self.assertEqual(forms.get_image_size("https://l10n.gnome.org/static/img/nobody.png";), (48, 48))
 
     def test_invalid_images(self):
         invalid_urls = (
@@ -267,36 +247,29 @@ class PeopleTestCase(TestCase):
         The order of the languages should be case insensitive.
         This tests that "français" appears before "Gaeilge".
         """
-        pn = Person.objects.create(username='jn')
+        pn = Person.objects.create(username="jn")
         self.client.force_login(pn)
-        url = reverse('person_detail_username', kwargs={'slug': 'jn'})
+        url = reverse("person_detail_username", kwargs={"slug": "jn"})
         response = self.client.get(url)
-        all_languages = response.context['all_languages']
+        all_languages = response.context["all_languages"]
         self.assertGreater(
-            all_languages.index(('ga', 'Gaeilge')),
-            all_languages.index(('fr', "français")),
-            "français is not before Frisian"
+            all_languages.index(("ga", "Gaeilge")),
+            all_languages.index(("fr", "français")),
+            "français is not before Frisian",
         )
 
 
 class PeopleViewsTestCase(TestCase):
-
     def setUp(self) -> None:
-        self.coordinator = Person.objects.create(
-            id=1, username="coordinator"
-        )
-        self.reviewer = Person.objects.create(
-            id=2, username="reviewer"
-        )
+        self.coordinator = Person.objects.create(id=1, username="coordinator")
+        self.reviewer = Person.objects.create(id=2, username="reviewer")
         self.client = Client()
 
     def test_auth_token_displayed_on_own_page(self):
         self.client.force_login(self.coordinator)
         auth_token = self.coordinator.auth_token = Person.generate_token()
         self.coordinator.save()
-        response = self.client.get(reverse(
-            "person_detail_username", kwargs={"slug": self.coordinator.username}
-        ))
+        response = self.client.get(reverse("person_detail_username", kwargs={"slug": 
self.coordinator.username}))
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.context["person"].auth_token, auth_token)
         self.assertTrue(response.context["on_own_page"])
@@ -306,9 +279,7 @@ class PeopleViewsTestCase(TestCase):
         self.client.force_login(self.reviewer)
         auth_token = self.coordinator.auth_token = Person.generate_token()
         self.coordinator.save()
-        response = self.client.get(reverse(
-            "person_detail_username", kwargs={"slug": self.coordinator.username}
-        ))
+        response = self.client.get(reverse("person_detail_username", kwargs={"slug": 
self.coordinator.username}))
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.context["person"].auth_token, auth_token)
         self.assertFalse(response.context["on_own_page"])
diff --git a/people/urls.py b/people/urls.py
index 24708702..3324fa43 100644
--- a/people/urls.py
+++ b/people/urls.py
@@ -3,34 +3,16 @@ from django.urls import path
 
 from people import views
 
-
 # Path order is really important here
 urlpatterns = [
-    path('detail_change/',
-         login_required(views.PersonEditView.as_view()),
-         name='person_detail_change'),
-    path('password_change/',
-         views.person_password_change,
-         name='person_password_change'),
-    path('create_token/',
-         views.person_create_token,
-         name='person_create_token'),
-    path('delete_token/',
-         views.person_delete_token,
-         name='person_delete_token'),
-    path('team_join/',
-         views.person_team_join,
-         name='person_team_join'),
-    path('team_leave/<locale:team_slug>/',
-         views.person_team_leave,
-         name='person_team_leave'),
-    path('<int:pk>/',
-         views.PersonDetailView.as_view(),
-         name='person_detail_id'),
+    path("detail_change/", login_required(views.PersonEditView.as_view()), name="person_detail_change"),
+    path("password_change/", views.person_password_change, name="person_password_change"),
+    path("create_token/", views.person_create_token, name="person_create_token"),
+    path("delete_token/", views.person_delete_token, name="person_delete_token"),
+    path("team_join/", views.person_team_join, name="person_team_join"),
+    path("team_leave/<locale:team_slug>/", views.person_team_leave, name="person_team_leave"),
+    path("<int:pk>/", views.PersonDetailView.as_view(), name="person_detail_id"),
     # Equivalent to the previous, but using username instead of user pk
-    path('<slug>/',
-         views.PersonDetailView.as_view(),
-         name='person_detail_username'),
-
-    path('', views.PeopleListView.as_view(), name='people'),
+    path("<slug>/", views.PersonDetailView.as_view(), name="person_detail_username"),
+    path("", views.PeopleListView.as_view(), name="people"),
 ]
diff --git a/people/views.py b/people/views.py
index aeb4f46d..dc2bf096 100644
--- a/people/views.py
+++ b/people/views.py
@@ -2,21 +2,22 @@ from operator import itemgetter
 
 from django.conf import settings
 from django.conf.locale import LANG_INFO
+from django.contrib import messages
 from django.contrib.auth.decorators import login_required
 from django.contrib.auth.forms import PasswordChangeForm
-from django.contrib import messages
 from django.db import IntegrityError, transaction
 from django.http import HttpResponseRedirect
-from django.shortcuts import render, get_object_or_404
+from django.shortcuts import get_object_or_404, render
 from django.urls import reverse
-from django.utils.translation import gettext_lazy, gettext as _
+from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy
 from django.views.decorators.http import require_POST
-from django.views.generic import ListView, DetailView, UpdateView
+from django.views.generic import DetailView, ListView, UpdateView
 
 from common.utils import lc_sorted
+from people.forms import DetailForm, TeamJoinForm
 from people.models import Person
-from teams.models import Team, Role
-from people.forms import TeamJoinForm, DetailForm
+from teams.models import Role, Team
 from vertimus.models import State
 
 
@@ -25,45 +26,48 @@ class PeopleListView(ListView):
 
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
-        context['pageSection'] = "teams"
+        context["pageSection"] = "teams"
         return context
 
 
 class PersonDetailView(DetailView):
     model = Person
-    slug_field = 'username'
-    context_object_name = 'person'
+    slug_field = "username"
+    context_object_name = "person"
 
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
         states = State.objects.filter(action__person=self.object).distinct()
         all_languages = [
-            (lg[0], LANG_INFO.get(lg[0], {'name_local': lg[1]})['name_local']) for lg in settings.LANGUAGES
+            (lg[0], LANG_INFO.get(lg[0], {"name_local": lg[1]})["name_local"]) for lg in settings.LANGUAGES
         ]
         all_languages = lc_sorted(all_languages, key=itemgetter(1))
-        context.update({
-            'pageSection': "teams",
-            'all_languages': all_languages,
-            'on_own_page': self.request.user.is_authenticated and self.object.username == 
self.request.user.username,
-            'states': [(s, s.stats) for s in states],
-        })
+        context.update(
+            {
+                "pageSection": "teams",
+                "all_languages": all_languages,
+                "on_own_page": self.request.user.is_authenticated
+                and self.object.username == self.request.user.username,
+                "states": [(s, s.stats) for s in states],
+            }
+        )
         return context
 
 
 class PersonEditView(UpdateView):
     model = Person
-    slug_field = 'username'
+    slug_field = "username"
     form_class = DetailForm
-    template_name = 'people/person_detail_change_form.html'
+    template_name = "people/person_detail_change_form.html"
 
     def get_object(self, **kwargs):
-        self.kwargs['slug'] = self.request.user.username
+        self.kwargs["slug"] = self.request.user.username
         return super().get_object()
 
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
-        context['pageSection'] = "teams"
-        context['on_own_page'] = self.object.username == self.request.user.username
+        context["pageSection"] = "teams"
+        context["on_own_page"] = self.object.username == self.request.user.username
         return context
 
     def form_invalid(self, form):
@@ -75,10 +79,10 @@ class PersonEditView(UpdateView):
 def person_team_join(request):
     """Handle the form to join a team"""
     person = get_object_or_404(Person, username=request.user.username)
-    if request.method == 'POST':
+    if request.method == "POST":
         form = TeamJoinForm(request.POST)
         if form.is_valid():
-            team = form.cleaned_data['teams']
+            team = form.cleaned_data["teams"]
             # Role default to 'translator'
             new_role = Role(team=team, person=person)
             try:
@@ -88,7 +92,7 @@ def person_team_join(request):
                 team.send_mail_to_coordinator(
                     subject=gettext_lazy("A new person joined your team"),
                     message=gettext_lazy("%(name)s has just joined your translation team on %(site)s"),
-                    messagekw={'name': person.name, 'site': settings.SITE_DOMAIN}
+                    messagekw={"name": person.name, "site": settings.SITE_DOMAIN},
                 )
             except IntegrityError:
                 messages.info(request, _("You are already member of this team."))
@@ -96,12 +100,12 @@ def person_team_join(request):
         form = TeamJoinForm()
 
     context = {
-        'pageSection': "teams",
-        'person': person,
-        'on_own_page': person.username == request.user.username,
-        'form': form,
+        "pageSection": "teams",
+        "person": person,
+        "on_own_page": person.username == request.user.username,
+        "form": form,
     }
-    return render(request, 'people/person_team_join_form.html', context)
+    return render(request, "people/person_team_join_form.html", context)
 
 
 @login_required
@@ -117,9 +121,7 @@ def person_team_leave(request, team_slug):
         # Message no i18n'ed, should never happen under normal conditions
         messages.error(request, _("You are not a member of this team."))
     # redirect to normal person detail
-    return HttpResponseRedirect(
-        reverse('person_detail_username', args=(person.username,))
-    )
+    return HttpResponseRedirect(reverse("person_detail_username", args=(person.username,)))
 
 
 @login_required
@@ -127,7 +129,7 @@ def person_password_change(request):
     """Handle the generic form to change the password"""
     person = get_object_or_404(Person, username=request.user.username)
 
-    if request.method == 'POST':
+    if request.method == "POST":
         form = PasswordChangeForm(request.user, request.POST)
         if form.is_valid():
             messages.success(request, _("Your password has been changed."))
@@ -136,12 +138,12 @@ def person_password_change(request):
         form = PasswordChangeForm(request.user)
 
     context = {
-        'pageSection': "teams",
-        'person': person,
-        'on_own_page': person.username == request.user.username,
-        'form': form,
+        "pageSection": "teams",
+        "person": person,
+        "on_own_page": person.username == request.user.username,
+        "form": form,
     }
-    return render(request, 'people/person_password_change_form.html', context)
+    return render(request, "people/person_password_change_form.html", context)
 
 
 @login_required
@@ -150,13 +152,13 @@ def person_create_token(request):
     person = get_object_or_404(Person, username=request.user.username)
     person.auth_token = Person.generate_token()
     person.save()
-    return HttpResponseRedirect(reverse('person_detail_username', args=[person.username]))
+    return HttpResponseRedirect(reverse("person_detail_username", args=[person.username]))
 
 
 @login_required
 @require_POST
 def person_delete_token(request):
     person = get_object_or_404(Person, username=request.user.username)
-    person.auth_token = ''
+    person.auth_token = ""
     person.save()
-    return HttpResponseRedirect(reverse('person_detail_username', args=[person.username]))
+    return HttpResponseRedirect(reverse("person_detail_username", args=[person.username]))
diff --git a/pyproject.toml b/pyproject.toml
index fa7093a3..7d32a566 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,3 +1,9 @@
 [build-system]
 requires = ["setuptools>=42"]
-build-backend = "setuptools.build_meta"
\ No newline at end of file
+build-backend = "setuptools.build_meta"
+
+[tool.black]
+line-length = 119
+
+[tool.isort]
+profile = "black"
diff --git a/setup.py b/setup.py
index 080528a1..4a3514f1 100755
--- a/setup.py
+++ b/setup.py
@@ -2,9 +2,8 @@
 
 import os
 import re
-from typing import List
-
 from datetime import datetime
+from typing import List
 
 from setuptools import setup
 
@@ -25,7 +24,7 @@ def get_requirements() -> List[str]:
 
 
 setup(
-    version=datetime.utcnow().strftime('%Y%m%d%H%M'),
+    version=datetime.utcnow().strftime("%Y%m%d%H%M"),
     packages=["."],
     install_requires=get_requirements(),
 )
diff --git a/stats/admin.py b/stats/admin.py
index 149a39ef..305112b5 100644
--- a/stats/admin.py
+++ b/stats/admin.py
@@ -1,35 +1,40 @@
 from django import forms
-from django.core.exceptions import PermissionDenied
 from django.contrib import admin, messages
 from django.contrib.admin import helpers
+from django.core.exceptions import PermissionDenied
 from django.db import transaction
 from django.http import HttpResponseRedirect
 from django.shortcuts import render
 
 from stats.doap import update_doap_infos
 from stats.models import (
-    Statistics, Information, PoFile, Module, Branch, Domain, Category,
-    CategoryName, Release,
+    Branch,
+    Category,
+    CategoryName,
+    Domain,
+    Information,
+    Module,
+    PoFile,
+    Release,
+    Statistics,
 )
 
 
 class DomainForm(forms.ModelForm):
     class Meta:
         model = Module
-        fields = '__all__'
+        fields = "__all__"
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         if self.instance.pk:
-            self.fields['branch_from'].queryset = self.fields['branch_from'].queryset.filter(
-                module=self.instance.module
-            )
-            self.fields['branch_to'].queryset = self.fields['branch_to'].queryset.filter(
+            self.fields["branch_from"].queryset = self.fields["branch_from"].queryset.filter(
                 module=self.instance.module
             )
+            self.fields["branch_to"].queryset = 
self.fields["branch_to"].queryset.filter(module=self.instance.module)
         else:
-            self.fields['branch_from'].queryset = Branch.objects.none()
-            self.fields['branch_to'].queryset = Branch.objects.none()
+            self.fields["branch_from"].queryset = Branch.objects.none()
+            self.fields["branch_to"].queryset = Branch.objects.none()
 
 
 class BranchInline(admin.TabularInline):
@@ -40,15 +45,20 @@ class DomainInline(admin.StackedInline):
     model = Domain
     form = DomainForm
     fieldsets = (
-        (None, {
-            'fields': (('name', 'description', 'dtype', 'layout'),)
-        }),
-        ('Advanced', {
-            'fields': (('pot_method', 'pot_params'),
-                       'extra_its_dirs', 'linguas_location', 'red_filter',
-                       ('branch_from', 'branch_to'),),
-            'classes': ('collapse',),
-        }),
+        (None, {"fields": (("name", "description", "dtype", "layout"),)}),
+        (
+            "Advanced",
+            {
+                "fields": (
+                    ("pot_method", "pot_params"),
+                    "extra_its_dirs",
+                    "linguas_location",
+                    "red_filter",
+                    ("branch_from", "branch_to"),
+                ),
+                "classes": ("collapse",),
+            },
+        ),
     )
 
     def get_formset(self, request, obj=None, **kwargs):
@@ -57,27 +67,27 @@ class DomainInline(admin.StackedInline):
         return super().get_formset(request, obj, **kwargs)
 
     def formfield_for_dbfield(self, db_field, **kwargs):
-        if db_field.name == 'description':
-            kwargs['widget'] = forms.Textarea(attrs={'rows': '1', 'cols': '20'})
-        elif db_field.name in ('name', 'layout'):
-            kwargs['widget'] = forms.TextInput(attrs={'size': '20'})
-        elif db_field.name in ('red_filter', 'extra_its_dirs'):
-            kwargs['widget'] = forms.Textarea(attrs={'rows': '1', 'cols': '40'})
+        if db_field.name == "description":
+            kwargs["widget"] = forms.Textarea(attrs={"rows": "1", "cols": "20"})
+        elif db_field.name in ("name", "layout"):
+            kwargs["widget"] = forms.TextInput(attrs={"size": "20"})
+        elif db_field.name in ("red_filter", "extra_its_dirs"):
+            kwargs["widget"] = forms.Textarea(attrs={"rows": "1", "cols": "40"})
         return super().formfield_for_dbfield(db_field, **kwargs)
 
     def formfield_for_foreignkey(self, db_field, request, **kwargs):
-        if db_field.name in ('branch_from', 'branch_to') and hasattr(self, 'parent_obj') and self.parent_obj:
-            kwargs['queryset'] = self.parent_obj.branch_set.all()
+        if db_field.name in ("branch_from", "branch_to") and hasattr(self, "parent_obj") and self.parent_obj:
+            kwargs["queryset"] = self.parent_obj.branch_set.all()
         return super().formfield_for_foreignkey(db_field, request, **kwargs)
 
 
 class ModuleForm(forms.ModelForm):
     class Meta:
         model = Module
-        fields = '__all__'
+        fields = "__all__"
 
     def save(self, **kwargs):
-        must_renew_checkout = 'vcs_root' in self.changed_data and not self.instance._state.adding and not 
self.errors
+        must_renew_checkout = "vcs_root" in self.changed_data and not self.instance._state.adding and not 
self.errors
         if must_renew_checkout:
             old_module = Module.objects.get(pk=self.instance.pk)
             # Delete checkout(s)
@@ -94,24 +104,32 @@ class ModuleForm(forms.ModelForm):
 class ModuleAdmin(admin.ModelAdmin):
     form = ModuleForm
     fieldsets = (
-        (None, {
-            'fields': (('name', 'description'),
-                       'homepage', 'comment', 'archived',
-                       ('bugs_base',),
-                       ('vcs_type', 'vcs_root', 'vcs_web'),
-                       'ext_platform', 'maintainers')
-        }),
+        (
+            None,
+            {
+                "fields": (
+                    ("name", "description"),
+                    "homepage",
+                    "comment",
+                    "archived",
+                    ("bugs_base",),
+                    ("vcs_type", "vcs_root", "vcs_web"),
+                    "ext_platform",
+                    "maintainers",
+                )
+            },
+        ),
     )
     inlines = [BranchInline, DomainInline]
-    search_fields = ('name', 'homepage', 'comment', 'vcs_web')
-    list_filter = ('archived',)
+    search_fields = ("name", "homepage", "comment", "vcs_web")
+    list_filter = ("archived",)
 
     def formfield_for_dbfield(self, db_field, **kwargs):
         field = super().formfield_for_dbfield(db_field, **kwargs)
-        if db_field.name == 'description':
-            field.widget.attrs['rows'] = '1'
-        elif db_field.name == 'comment':
-            field.widget.attrs['rows'] = '4'
+        if db_field.name == "description":
+            field.widget.attrs["rows"] = "1"
+        elif db_field.name == "comment":
+            field.widget.attrs["rows"] = "4"
 
         return field
 
@@ -122,45 +140,47 @@ class ModuleAdmin(admin.ModelAdmin):
 
 
 class BranchAdmin(admin.ModelAdmin):
-    search_fields = ('name', 'module__name')
+    search_fields = ("name", "module__name")
 
 
 class DomainAdmin(admin.ModelAdmin):
     form = DomainForm
-    list_display = ('__str__', 'layout', 'pot_method')
-    list_filter = ('dtype', 'pot_method')
-    search_fields = ('name', 'module__name', 'layout', 'pot_method')
+    list_display = ("__str__", "layout", "pot_method")
+    list_filter = ("dtype", "pot_method")
+    search_fields = ("name", "module__name", "layout", "pot_method")
 
 
 class CategoryInline(admin.TabularInline):
     model = Category
-    raw_id_fields = ('branch',)  # Too costly otherwise
+    raw_id_fields = ("branch",)  # Too costly otherwise
     extra = 1
 
 
 class CategoryAdmin(admin.ModelAdmin):
-    search_fields = ('name__name', 'branch__module__name')
+    search_fields = ("name__name", "branch__module__name")
 
 
 class ReleaseAdmin(admin.ModelAdmin):
-    list_display = ('name', 'status', 'weight', 'string_frozen')
-    list_editable = ('weight',)
+    list_display = ("name", "status", "weight", "string_frozen")
+    list_editable = ("weight",)
     inlines = [CategoryInline]
-    actions = ['copy_release', 'delete_release']
+    actions = ["copy_release", "delete_release"]
 
     def copy_release(self, request, queryset):
         """Copy an existing release and use master branches"""
         if not self.has_add_permission(request):
             raise PermissionDenied
         if queryset.count() > 1:
-            messages.error(request, 'Please copy only one release at a time')
+            messages.error(request, "Please copy only one release at a time")
             return HttpResponseRedirect(request.path)
 
         base_rel = queryset.first()
         with transaction.atomic():
             new_rel = Release.objects.create(
-                name=base_rel.name + '-copy', description=base_rel.description + '-copy',
-                string_frozen=False, status=base_rel.status
+                name=base_rel.name + "-copy",
+                description=base_rel.description + "-copy",
+                string_frozen=False,
+                status=base_rel.status,
             )
 
             branch_seen = set()
@@ -180,10 +200,10 @@ class ReleaseAdmin(admin.ModelAdmin):
     copy_release.short_description = "Copy release (and associated branches)"
 
     def delete_release(self, request, queryset):
-        """ Admin action to delete releases *with* branches which are not linked to another release """
+        """Admin action to delete releases *with* branches which are not linked to another release"""
         if not self.has_delete_permission(request):
             raise PermissionDenied
-        if request.POST.get('post'):
+        if request.POST.get("post"):
             # Already confirmed
             for obj in queryset:
                 self.log_deletion(request, obj, str(obj))
@@ -196,9 +216,14 @@ class ReleaseAdmin(admin.ModelAdmin):
                         branch.delete()
                         b += 1
             queryset.delete()
-            self.message_user(request, "Successfully deleted %(countr)d release(s) and %(countb)d 
branch(es)." % {
-                "countr": n, "countb": b,
-            })
+            self.message_user(
+                request,
+                "Successfully deleted %(countr)d release(s) and %(countb)d branch(es)."
+                % {
+                    "countr": n,
+                    "countb": b,
+                },
+            )
             # Return None to display the change list page again.
             return None
         context = {
@@ -208,7 +233,7 @@ class ReleaseAdmin(admin.ModelAdmin):
             "model_label": self.model._meta.verbose_name_plural,
             "action_checkbox_name": helpers.ACTION_CHECKBOX_NAME,
         }
-        return render(request, 'admin/delete_release_confirmation.html', context)
+        return render(request, "admin/delete_release_confirmation.html", context)
 
     delete_release.short_description = "Delete release (and associated branches)"
 
@@ -219,13 +244,13 @@ class InformationInline(admin.TabularInline):
 
 
 class StatisticsAdmin(admin.ModelAdmin):
-    search_fields = ('language__name', 'branch__module__name')
-    raw_id_fields = ('branch', 'domain', 'language', 'full_po', 'part_po')
+    search_fields = ("language__name", "branch__module__name")
+    raw_id_fields = ("branch", "domain", "language", "full_po", "part_po")
     inlines = [InformationInline]
 
 
 class PoFileAdmin(admin.ModelAdmin):
-    search_fields = ('path',)
+    search_fields = ("path",)
 
 
 admin.site.register(Statistics, StatisticsAdmin)
diff --git a/stats/doap.py b/stats/doap.py
index af20ca68..a88da4fc 100644
--- a/stats/doap.py
+++ b/stats/doap.py
@@ -27,28 +27,28 @@ class DoapParser:
         maintainers = []
         for maint in maint_tags:
             parsed = dict(
-                (key, res) for key, res in [
-                    (attr.split('}')[-1], maint[0].find(attr)) for attr in pers_attrs
-                ] if res is not None
+                (key, res)
+                for key, res in [(attr.split("}")[-1], maint[0].find(attr)) for attr in pers_attrs]
+                if res is not None
             )
-            maint = {'name': parsed['name'].text, 'email': None, 'account': None}
-            if 'mbox' in parsed:
-                maint['email'] = unquote(parsed['mbox'].items()[0][1].replace("mailto:";, ""))
-            if 'userid' in parsed:
-                maint['account'] = parsed['userid'].text
+            maint = {"name": parsed["name"].text, "email": None, "account": None}
+            if "mbox" in parsed:
+                maint["email"] = unquote(parsed["mbox"].items()[0][1].replace("mailto:";, ""))
+            if "userid" in parsed:
+                maint["account"] = parsed["userid"].text
             maintainers.append(maint)
         return maintainers
 
     def parse_homepage(self):
         homepage_tag = self.project_root.find("{http://usefulinc.com/ns/doap#}homepage";)
         if homepage_tag is not None:
-            return homepage_tag.attrib.get('{http://www.w3.org/1999/02/22-rdf-syntax-ns#}resource')
+            return homepage_tag.attrib.get("{http://www.w3.org/1999/02/22-rdf-syntax-ns#}resource";)
         return None
 
 
 def update_doap_infos(module):
-    """ Should only be called inside an "update-stats" context of a master branch,
-        so there is no need for any extra checkout/locking strategy """
+    """Should only be called inside an "update-stats" context of a master branch,
+    so there is no need for any extra checkout/locking strategy"""
     doap_path = module.get_head_branch().co_path / ("%s.doap" % module.name)
     if not doap_path.exists():
         return
@@ -59,25 +59,29 @@ def update_doap_infos(module):
 
     # *********** Update maintainers
     def slugify(val):
-        value = re.sub(r'[^\w\s-]', '', val).strip().lower()
-        return re.sub(r'[-\s]+', '-', value)
+        value = re.sub(r"[^\w\s-]", "", val).strip().lower()
+        return re.sub(r"[-\s]+", "-", value)
+
     doap_maintainers = tree.parse_maintainers()
     current_maintainers = dict([(m.email or m.username, m) for m in module.maintainers.all()])
 
     # Using email or username as unique identifier
     for maintainer in doap_maintainers:
-        maintainer_key = maintainer['email'] or maintainer['account'] or slugify(maintainer['name'])
+        maintainer_key = maintainer["email"] or maintainer["account"] or slugify(maintainer["name"])
         if maintainer_key in current_maintainers.keys():
             del current_maintainers[maintainer_key]
         else:
             # Add new maintainer
-            pers = Person.get_by_attr('email', maintainer['email'])
+            pers = Person.get_by_attr("email", maintainer["email"])
             if not pers:
-                pers = Person.get_by_attr('username', maintainer['account'] or slugify(maintainer['name']))
+                pers = Person.get_by_attr("username", maintainer["account"] or slugify(maintainer["name"]))
             if not pers:
                 pers = Person(
-                    username=maintainer['account'] or slugify(maintainer['name']), email=maintainer['email'] 
or '',
-                    password='!', svn_account=maintainer['account'], last_name=maintainer['name']
+                    username=maintainer["account"] or slugify(maintainer["name"]),
+                    email=maintainer["email"] or "",
+                    password="!",
+                    svn_account=maintainer["account"],
+                    last_name=maintainer["name"],
                 )
                 pers.save()
             module.maintainers.add(pers)
diff --git a/stats/forms.py b/stats/forms.py
index d0190ef6..31e3e42c 100644
--- a/stats/forms.py
+++ b/stats/forms.py
@@ -7,9 +7,9 @@ from stats.models import Branch, Category, CategoryName, Release
 
 class ReleaseField(forms.ModelChoiceField):
     def __init__(self, *args, **kwargs):
-        kwargs['required'] = False
+        kwargs["required"] = False
         super().__init__(*args, **kwargs)
-        if 'label' in kwargs:
+        if "label" in kwargs:
             self.is_branch = True
 
 
@@ -20,35 +20,29 @@ class ModuleBranchForm(forms.Form):
         self.branch_fields = []
         default_cat_name = None
         for branch in module.get_branches(reverse=True):
-            categories = list(branch.category_set.order_by('name', 'release__name'))
+            categories = list(branch.category_set.order_by("name", "release__name"))
             if len(categories) > 0:
                 for category in categories:
                     self.fields[str(category.id)] = ReleaseField(
                         queryset=Release.objects.all(), label=branch.name, initial=category.release.pk
                     )
-                    self.fields[str(category.id) + '_cat'] = forms.ModelChoiceField(
-                        queryset=CategoryName.objects.all(),
-                        required=False,
-                        initial=category.name
+                    self.fields[str(category.id) + "_cat"] = forms.ModelChoiceField(
+                        queryset=CategoryName.objects.all(), required=False, initial=category.name
                     )
-                    self.branch_fields.append((str(category.id), f'{category.id}_cat'))
+                    self.branch_fields.append((str(category.id), f"{category.id}_cat"))
                 default_cat_name = categories[-1].name
             else:
                 # Branch is not linked to any release
-                self.fields[branch.name] = ReleaseField(queryset=Release.objects.all(),
-                                                        label=branch.name)
-                self.fields[branch.name + '_cat'] = forms.ModelChoiceField(
-                    queryset=CategoryName.objects.all(),
-                    required=False,
-                    initial=default_cat_name
+                self.fields[branch.name] = ReleaseField(queryset=Release.objects.all(), label=branch.name)
+                self.fields[branch.name + "_cat"] = forms.ModelChoiceField(
+                    queryset=CategoryName.objects.all(), required=False, initial=default_cat_name
                 )
-                self.branch_fields.append((branch.name, branch.name + '_cat'))
+                self.branch_fields.append((branch.name, branch.name + "_cat"))
 
-        self.fields['new_branch'] = forms.CharField(required=False)
-        self.fields['new_branch_release'] = ReleaseField(queryset=Release.objects.all())
-        self.fields['new_branch_category'] = forms.ModelChoiceField(
-            queryset=CategoryName.objects.all(),
-            initial=default_cat_name
+        self.fields["new_branch"] = forms.CharField(required=False)
+        self.fields["new_branch_release"] = ReleaseField(queryset=Release.objects.all())
+        self.fields["new_branch_category"] = forms.ModelChoiceField(
+            queryset=CategoryName.objects.all(), initial=default_cat_name
         )
 
     def get_branches(self):
@@ -59,31 +53,30 @@ class ModuleBranchForm(forms.Form):
         cleaned_data = super().clean()
         for field_name in list(cleaned_data.keys()):
             if (
-                field_name.endswith('_cat')
+                field_name.endswith("_cat")
                 and cleaned_data[field_name] is None
                 and cleaned_data[field_name[:-4]] is not None
             ):
                 self.add_error(
-                    field_name,
-                    ValidationError(_("You have to provide a category when a version is specified."))
+                    field_name, ValidationError(_("You have to provide a category when a version is 
specified."))
                 )
-        if cleaned_data['new_branch']:
+        if cleaned_data["new_branch"]:
             try:
-                branch = Branch.objects.get(module=self.module, name=cleaned_data['new_branch'])
+                branch = Branch.objects.get(module=self.module, name=cleaned_data["new_branch"])
             except Branch.DoesNotExist:
                 # The branch will be created later in the view
                 return cleaned_data
-            if not cleaned_data['new_branch_release']:
+            if not cleaned_data["new_branch_release"]:
                 self.add_error(
-                    'new_branch_release',
+                    "new_branch_release",
                     ValidationError(
                         "There is already an entry for branch %s. Edit that one if "
                         "you want to remove the release link." % branch
-                    )
+                    ),
                 )
             else:
-                rel = Release.objects.get(pk=cleaned_data['new_branch_release'].pk)
-                cat = Category(release=rel, branch=branch, name=cleaned_data['new_branch_category'])
+                rel = Release.objects.get(pk=cleaned_data["new_branch_release"].pk)
+                cat = Category(release=rel, branch=branch, name=cleaned_data["new_branch_category"])
                 cat.full_clean()
-                cleaned_data['new_category'] = cat
+                cleaned_data["new_category"] = cat
         return cleaned_data
diff --git a/stats/management/commands/archive-release.py b/stats/management/commands/archive-release.py
index 160457d4..b2657fef 100644
--- a/stats/management/commands/archive-release.py
+++ b/stats/management/commands/archive-release.py
@@ -10,32 +10,32 @@ class Command(BaseCommand):
     help = "Write statistics of a release in a CSV structure (stdout)"
 
     def add_arguments(self, parser):
-        parser.add_argument('release')
+        parser.add_argument("release")
 
     def handle(self, **options):
         try:
-            release = Release.objects.get(name=options['release'])
+            release = Release.objects.get(name=options["release"])
         except Release.DoesNotExist:
-            raise CommandError("The release name '%s' is not known.\n" % options['release'])
+            raise CommandError("The release name '%s' is not known.\n" % options["release"])
 
         out = StringIO()
-        headers = ['Module', 'Branch', 'Domain', 'Language', 'Translated', 'Fuzzy', 'Untranslated']
+        headers = ["Module", "Branch", "Domain", "Language", "Translated", "Fuzzy", "Untranslated"]
         writer = csv.DictWriter(out, headers)
         header = dict(zip(headers, headers))
         writer.writerow(header)
 
-        stats = Statistics.objects.filter(
-            branch__category__release=release, language__isnull=False
-            ).select_related('branch__module', 'domain', 'language')
+        stats = Statistics.objects.filter(branch__category__release=release, 
language__isnull=False).select_related(
+            "branch__module", "domain", "language"
+        )
         for stat in stats:
             row = {
-                'Module': stat.branch.module.name,
-                'Branch': stat.branch.name,
-                'Domain': stat.domain.name,
-                'Language': stat.language.locale,
-                'Translated': stat.translated(),
-                'Fuzzy': stat.fuzzy(),
-                'Untranslated': stat.untranslated(),
+                "Module": stat.branch.module.name,
+                "Branch": stat.branch.name,
+                "Domain": stat.domain.name,
+                "Language": stat.language.locale,
+                "Translated": stat.translated(),
+                "Fuzzy": stat.fuzzy(),
+                "Untranslated": stat.untranslated(),
             }
             writer.writerow(row)
         return out.getvalue()
diff --git a/stats/management/commands/compile-trans.py b/stats/management/commands/compile-trans.py
index 7954aec1..c0053d40 100644
--- a/stats/management/commands/compile-trans.py
+++ b/stats/management/commands/compile-trans.py
@@ -13,31 +13,28 @@ class Command(BaseCommand):
     def add_arguments(self, parser):
         # Copy --locale arg from compilemessages
         parser.add_argument(
-            '--locale', '-l', action='append', default=[],
-            help='Locale(s) to process (e.g. de_AT). Default is to process all. '
-                 'Can be used multiple times.',
+            "--locale",
+            "-l",
+            action="append",
+            default=[],
+            help="Locale(s) to process (e.g. de_AT). Default is to process all. " "Can be used multiple 
times.",
         )
 
     def handle(self, **options):
         # Copy all po/ll.po files in locale/ll/LC_MESSAGES/django.po
-        podir = settings.BASE_DIR / 'po'
+        podir = settings.BASE_DIR / "po"
         for pofile in podir.iterdir():
             if pofile.suffix != ".po":
                 continue
             lang_code = pofile.stem
-            if options['locale'] and lang_code not in options['locale']:
+            if options["locale"] and lang_code not in options["locale"]:
                 continue
-            localedir = (
-                settings.BASE_DIR / 'locale' / Language.django_locale(lang_code) / 'LC_MESSAGES'
-            )
+            localedir = settings.BASE_DIR / "locale" / Language.django_locale(lang_code) / "LC_MESSAGES"
             if not localedir.is_dir():
                 localedir.mkdir(parents=True, exist_ok=True)
-            if options['verbosity'] > 1:
+            if options["verbosity"] > 1:
                 self.stdout.write(f"Copying {pofile} to {localedir}")
-            shutil.copy(pofile, localedir / 'django.po')
+            shutil.copy(pofile, localedir / "django.po")
 
         # Run compilemessages -l ll
-        call_command(
-            'compilemessages',
-            locale=[Language.django_locale(locale) for locale in options['locale']]
-        )
+        call_command("compilemessages", locale=[Language.django_locale(locale) for locale in 
options["locale"]])
diff --git a/stats/management/commands/run-maintenance.py b/stats/management/commands/run-maintenance.py
index 24825ac9..e153fea1 100644
--- a/stats/management/commands/run-maintenance.py
+++ b/stats/management/commands/run-maintenance.py
@@ -1,9 +1,9 @@
 from django.core.management.base import BaseCommand
 
+from languages.views import clean_tar_files
 from people.models import Person
 from teams.models import Role
 from vertimus.models import ActionArchived
-from languages.views import clean_tar_files
 
 
 class Command(BaseCommand):
diff --git a/stats/management/commands/update-from-doap.py b/stats/management/commands/update-from-doap.py
index 00a47097..732949d1 100644
--- a/stats/management/commands/update-from-doap.py
+++ b/stats/management/commands/update-from-doap.py
@@ -1,18 +1,19 @@
 import sys
+
 from django.core.management.base import BaseCommand
 
-from stats.models import Module, ModuleLock
 from stats.doap import update_doap_infos
+from stats.models import Module, ModuleLock
 
 
 class Command(BaseCommand):
     help = "Update module information from doap file"
 
     def add_arguments(self, parser):
-        parser.add_argument('module', nargs='+')
+        parser.add_argument("module", nargs="+")
 
     def handle(self, **options):
-        for mod_name in options['module']:
+        for mod_name in options["module"]:
             try:
                 mod = Module.objects.get(name=mod_name)
             except Module.DoesNotExist:
diff --git a/stats/management/commands/update-stats.py b/stats/management/commands/update-stats.py
index fcd241c1..32994457 100644
--- a/stats/management/commands/update-stats.py
+++ b/stats/management/commands/update-stats.py
@@ -1,88 +1,87 @@
 import traceback
 
-from django.core.management.base import BaseCommand, CommandError
 from django.core.mail import mail_admins
+from django.core.management.base import BaseCommand, CommandError
 from django.db.models import Q
 
-from stats.models import Module, Branch, Release
+from stats.models import Branch, Module, Release
 
 
 class Command(BaseCommand):
     help = "Update statistics about po files"
 
     def add_arguments(self, parser):
-        parser.add_argument('module', nargs='?', default=None)
-        parser.add_argument('branch', nargs='*')
-        parser.add_argument(
-            '--release',
-            help="update all modules and branches linked to the specified release name"
-        )
-        parser.add_argument(
-            '--force', action='store_true', default=False,
-            help="force statistics generation, even if files didn't change"
-        )
+        parser.add_argument("module", nargs="?", default=None)
+        parser.add_argument("branch", nargs="*")
+        parser.add_argument("--release", help="update all modules and branches linked to the specified 
release name")
         parser.add_argument(
-            '--non-gnome', action='store_true', default=False,
-            help="generate statistics for non-gnome modules (externally hosted)"
+            "--force",
+            action="store_true",
+            default=False,
+            help="force statistics generation, even if files didn't change",
         )
         parser.add_argument(
-            '--debug', action='store_true', default=False,
-            help="activate interactive debug mode"
+            "--non-gnome",
+            action="store_true",
+            default=False,
+            help="generate statistics for non-gnome modules (externally hosted)",
         )
+        parser.add_argument("--debug", action="store_true", default=False, help="activate interactive debug 
mode")
 
     def handle(self, **options):
-        if options['debug']:
+        if options["debug"]:
             breakpoint()
-        self.force_stats = options['force']
-        if options['module'] and options['branch']:
+        self.force_stats = options["force"]
+        if options["module"] and options["branch"]:
             # Update the specific branch(es) of a module
             # This allows several modules (differently named) to point to the same vcs repo
             modules = Module.objects.filter(
-                Q(name=options['module']) | Q(vcs_root__endswith='/%s' % options['module'])
+                Q(name=options["module"]) | Q(vcs_root__endswith="/%s" % options["module"])
             )
             if not modules:
-                self.stderr.write("No modules match `%s`." % options['module'])
+                self.stderr.write("No modules match `%s`." % options["module"])
             for i, module in enumerate(modules):
                 if module.archived and not self.force_stats:
                     self.stderr.write("The module '%s' is archived. Skipping..." % module.name)
                     continue
                 branches = []
-                for branch_arg in options['branch']:
+                for branch_arg in options["branch"]:
                     if branch_arg == "trunk":
                         branch_arg = "HEAD"
 
                     try:
                         branches.append(module.branch_set.get(name=branch_arg))
                     except Branch.DoesNotExist:
-                        self.stderr.write("Unable to find branch '%s' for module '%s' in the database." % (
-                            branch_arg, module.name))
+                        self.stderr.write(
+                            "Unable to find branch '%s' for module '%s' in the database." % (branch_arg, 
module.name)
+                        )
                         continue
                 self.update_branches(branches, checkout=(i < 1))
 
-        elif options['module']:
+        elif options["module"]:
             # Update all branches of a module
             try:
-                module = Module.objects.get(name=options['module'])
+                module = Module.objects.get(name=options["module"])
             except Module.DoesNotExist:
-                raise CommandError("Unable to find a module named '%s' in the database" % options['module'])
+                raise CommandError("Unable to find a module named '%s' in the database" % options["module"])
             if module.archived and not self.force_stats:
                 self.stderr.write("The module '%s' is archived. Skipping..." % module.name)
             else:
                 self.update_branches(module.branch_set.all())
 
-        elif options['release']:
-            if options['module']:
+        elif options["release"]:
+            if options["module"]:
                 raise CommandError("The --release option cannot be combined with module/branch parameters.")
             try:
-                release = Release.objects.get(name=options['release'])
+                release = Release.objects.get(name=options["release"])
             except Release.DoesNotExist:
-                raise CommandError("Unable to find a release named '%s' in the database" % 
options['release'])
+                raise CommandError("Unable to find a release named '%s' in the database" % 
options["release"])
             self.update_branches(release.branches.all())
 
         else:
             # Update all modules
-            if options['non_gnome']:
-                modules = Module.objects.exclude(vcs_root__contains='.gnome.org')
+            if options["non_gnome"]:
+                modules = Module.objects.exclude(vcs_root__contains=".gnome.org")
             else:
                 modules = Module.objects.all()
             if not self.force_stats:
@@ -101,5 +100,4 @@ class Command(BaseCommand):
             except Exception as exc:
                 tbtext = traceback.format_exc()
                 mail_admins("Error while updating %s %s" % (branch.module.name, branch.name), tbtext)
-                self.stderr.write("Error while updating stats for %s %s: %s" % (
-                    branch.module.name, branch.name, exc))
+                self.stderr.write("Error while updating stats for %s %s: %s" % (branch.module.name, 
branch.name, exc))
diff --git a/stats/management/commands/update-trans.py b/stats/management/commands/update-trans.py
index 500e53e4..b2496e30 100644
--- a/stats/management/commands/update-trans.py
+++ b/stats/management/commands/update-trans.py
@@ -1,8 +1,7 @@
+import itertools
 import os
 import re
 import shutil
-
-import itertools
 import subprocess
 
 from django.conf import settings
@@ -11,7 +10,7 @@ from django.core.management.base import BaseCommand
 from django.db.models import F
 
 from languages.models import Language
-from stats.models import Module, Domain, Release, CategoryName
+from stats.models import CategoryName, Domain, Module, Release
 from teams.models import Team
 
 
@@ -27,7 +26,7 @@ class Command(BaseCommand):
         self.po_dir = settings.BASE_DIR / "po"
         self.locale_dir = settings.BASE_DIR / "locale" / self.lang_code / "LC_MESSAGES"
         if self.lang_code == "en":
-            self.po_file = self.po_dir / 'damned-lies.pot'
+            self.po_file = self.po_dir / "damned-lies.pot"
         else:
             self.po_file = self.po_dir / f"{self.lang_code}.po"
             self._update_django_po_with_code_strings()
diff --git a/stats/migrations/0001_initial.py b/stats/migrations/0001_initial.py
index 5def3d8b..13ab4bcb 100644
--- a/stats/migrations/0001_initial.py
+++ b/stats/migrations/0001_initial.py
@@ -1,208 +1,334 @@
-from django.db import models, migrations
 import django.db.models.deletion
+from django.db import migrations, models
+
 import common.fields
 
 
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('people', '0001_initial'),
-        ('languages', '0001_initial'),
+        ("people", "0001_initial"),
+        ("languages", "0001_initial"),
     ]
 
     operations = [
         migrations.CreateModel(
-            name='Branch',
+            name="Branch",
             fields=[
-                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, 
primary_key=True)),
-                ('name', models.CharField(max_length=50)),
-                ('vcs_subpath', models.CharField(max_length=50, null=True, blank=True)),
-                ('weight', models.IntegerField(default=0, help_text='Smaller weight is displayed first')),
-                ('file_hashes', common.fields.DictionaryField(default='', editable=False, blank=True)),
+                ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, 
primary_key=True)),
+                ("name", models.CharField(max_length=50)),
+                ("vcs_subpath", models.CharField(max_length=50, null=True, blank=True)),
+                ("weight", models.IntegerField(default=0, help_text="Smaller weight is displayed first")),
+                ("file_hashes", common.fields.DictionaryField(default="", editable=False, blank=True)),
             ],
             options={
-                'ordering': ('name',),
-                'db_table': 'branch',
-                'verbose_name_plural': 'branches',
+                "ordering": ("name",),
+                "db_table": "branch",
+                "verbose_name_plural": "branches",
             },
         ),
         migrations.CreateModel(
-            name='Category',
+            name="Category",
             fields=[
-                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, 
primary_key=True)),
-                ('name', models.CharField(default='default', max_length=30, choices=[('default', 'Default'), 
('admin-tools', 'Administration Tools'), ('dev-tools', 'Development Tools'), ('desktop', 'GNOME Desktop'), 
('dev-platform', 'GNOME Developer Platform'), ('proposed', 'New Module Proposals'), ('g3-core', 'Core'), 
('g3-utils', 'Utils'), ('g3-apps', 'Apps'), ('g3-a11y', 'Accessibility'), ('g3-games', 'Games'), 
('g3-backends', 'Backends'), ('g3-dev-tools', 'Development Tools'), ('g3-core-libs', 'Core Libraries'), 
('g3-extra-libs', 'Extra Libraries'), ('g2-legacy', 'Legacy Desktop'), ('stable', 'Stable Branches'), ('dev', 
'Development Branches')])),
-                ('branch', models.ForeignKey(to='stats.Branch', on_delete=models.CASCADE)),
+                ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, 
primary_key=True)),
+                (
+                    "name",
+                    models.CharField(
+                        default="default",
+                        max_length=30,
+                        choices=[
+                            ("default", "Default"),
+                            ("admin-tools", "Administration Tools"),
+                            ("dev-tools", "Development Tools"),
+                            ("desktop", "GNOME Desktop"),
+                            ("dev-platform", "GNOME Developer Platform"),
+                            ("proposed", "New Module Proposals"),
+                            ("g3-core", "Core"),
+                            ("g3-utils", "Utils"),
+                            ("g3-apps", "Apps"),
+                            ("g3-a11y", "Accessibility"),
+                            ("g3-games", "Games"),
+                            ("g3-backends", "Backends"),
+                            ("g3-dev-tools", "Development Tools"),
+                            ("g3-core-libs", "Core Libraries"),
+                            ("g3-extra-libs", "Extra Libraries"),
+                            ("g2-legacy", "Legacy Desktop"),
+                            ("stable", "Stable Branches"),
+                            ("dev", "Development Branches"),
+                        ],
+                    ),
+                ),
+                ("branch", models.ForeignKey(to="stats.Branch", on_delete=models.CASCADE)),
             ],
             options={
-                'db_table': 'category',
-                'verbose_name_plural': 'categories',
+                "db_table": "category",
+                "verbose_name_plural": "categories",
             },
         ),
         migrations.CreateModel(
-            name='Domain',
+            name="Domain",
             fields=[
-                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, 
primary_key=True)),
-                ('name', models.CharField(max_length=50)),
-                ('description', models.TextField(null=True, blank=True)),
-                ('dtype', models.CharField(default='ui', max_length=5, choices=[('ui', 'User Interface'), 
('doc', 'Documentation')])),
-                ('directory', models.CharField(max_length=50)),
-                ('pot_method', models.CharField(help_text="Leave blank for standard method (intltool for UI 
and gnome-doc-utils for DOC), or '&lt;gettext&gt;' for the pure xgettext-based extraction", max_length=100, 
null=True, blank=True)),
-                ('linguas_location', models.CharField(help_text="Use 'no' for no LINGUAS check, or 
path/to/file#variable for a non-standard location.\n            Leave blank for standard location 
(ALL_LINGUAS in LINGUAS/configure.ac/.in for UI and DOC_LINGUAS in Makefile.am for DOC)", max_length=50, 
null=True, blank=True)),
-                ('red_filter', models.TextField(help_text='pogrep filter to strip po file from unprioritized 
strings (format: location|string, "-" for no filter)', null=True, blank=True)),
+                ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, 
primary_key=True)),
+                ("name", models.CharField(max_length=50)),
+                ("description", models.TextField(null=True, blank=True)),
+                (
+                    "dtype",
+                    models.CharField(
+                        default="ui", max_length=5, choices=[("ui", "User Interface"), ("doc", 
"Documentation")]
+                    ),
+                ),
+                ("directory", models.CharField(max_length=50)),
+                (
+                    "pot_method",
+                    models.CharField(
+                        help_text="Leave blank for standard method (intltool for UI and gnome-doc-utils for 
DOC), or '&lt;gettext&gt;' for the pure xgettext-based extraction",
+                        max_length=100,
+                        null=True,
+                        blank=True,
+                    ),
+                ),
+                (
+                    "linguas_location",
+                    models.CharField(
+                        help_text="Use 'no' for no LINGUAS check, or path/to/file#variable for a 
non-standard location.\n            Leave blank for standard location (ALL_LINGUAS in 
LINGUAS/configure.ac/.in for UI and DOC_LINGUAS in Makefile.am for DOC)",
+                        max_length=50,
+                        null=True,
+                        blank=True,
+                    ),
+                ),
+                (
+                    "red_filter",
+                    models.TextField(
+                        help_text='pogrep filter to strip po file from unprioritized strings (format: 
location|string, "-" for no filter)',
+                        null=True,
+                        blank=True,
+                    ),
+                ),
             ],
             options={
-                'ordering': ('-dtype', 'name'),
-                'db_table': 'domain',
+                "ordering": ("-dtype", "name"),
+                "db_table": "domain",
             },
         ),
         migrations.CreateModel(
-            name='Information',
+            name="Information",
             fields=[
-                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, 
primary_key=True)),
-                ('type', models.CharField(max_length=10, choices=[('info', 'Information'), ('warn', 
'Warning'), ('error', 'Error'), ('warn-ext', 'Warning (external)'), ('error-ext', 'Error (external)')])),
-                ('description', models.TextField()),
+                ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, 
primary_key=True)),
+                (
+                    "type",
+                    models.CharField(
+                        max_length=10,
+                        choices=[
+                            ("info", "Information"),
+                            ("warn", "Warning"),
+                            ("error", "Error"),
+                            ("warn-ext", "Warning (external)"),
+                            ("error-ext", "Error (external)"),
+                        ],
+                    ),
+                ),
+                ("description", models.TextField()),
             ],
             options={
-                'db_table': 'information',
+                "db_table": "information",
             },
         ),
         migrations.CreateModel(
-            name='InformationArchived',
+            name="InformationArchived",
             fields=[
-                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, 
primary_key=True)),
-                ('type', models.CharField(max_length=10, choices=[('info', 'Information'), ('warn', 
'Warning'), ('error', 'Error'), ('warn-ext', 'Warning (external)'), ('error-ext', 'Error (external)')])),
-                ('description', models.TextField()),
+                ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, 
primary_key=True)),
+                (
+                    "type",
+                    models.CharField(
+                        max_length=10,
+                        choices=[
+                            ("info", "Information"),
+                            ("warn", "Warning"),
+                            ("error", "Error"),
+                            ("warn-ext", "Warning (external)"),
+                            ("error-ext", "Error (external)"),
+                        ],
+                    ),
+                ),
+                ("description", models.TextField()),
             ],
             options={
-                'db_table': 'information_archived',
+                "db_table": "information_archived",
             },
         ),
         migrations.CreateModel(
-            name='Module',
+            name="Module",
             fields=[
-                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, 
primary_key=True)),
-                ('name', models.SlugField(unique=True)),
-                ('homepage', models.URLField(help_text='Automatically updated if the module contains a doap 
file.', null=True, blank=True)),
-                ('description', models.TextField(null=True, blank=True)),
-                ('comment', models.TextField(null=True, blank=True)),
-                ('bugs_base', models.CharField(max_length=50, null=True, blank=True)),
-                ('bugs_product', models.CharField(max_length=50, null=True, blank=True)),
-                ('bugs_component', models.CharField(max_length=50, null=True, blank=True)),
-                ('vcs_type', models.CharField(max_length=5, choices=[('svn', 'Subversion'), ('git', 'Git'), 
('hg', 'Mercurial')])),
-                ('vcs_root', models.CharField(max_length=200)),
-                ('vcs_web', models.URLField()),
-                ('ext_platform', models.URLField(help_text='URL to external translation platform, if any', 
null=True, blank=True)),
-                ('archived', models.BooleanField(default=False)),
-                ('maintainers', models.ManyToManyField(help_text='Automatically updated if the module 
contains a doap file.', related_name='maintains_modules', db_table='module_maintainer', to='people.Person', 
blank=True)),
+                ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, 
primary_key=True)),
+                ("name", models.SlugField(unique=True)),
+                (
+                    "homepage",
+                    models.URLField(
+                        help_text="Automatically updated if the module contains a doap file.", null=True, 
blank=True
+                    ),
+                ),
+                ("description", models.TextField(null=True, blank=True)),
+                ("comment", models.TextField(null=True, blank=True)),
+                ("bugs_base", models.CharField(max_length=50, null=True, blank=True)),
+                ("bugs_product", models.CharField(max_length=50, null=True, blank=True)),
+                ("bugs_component", models.CharField(max_length=50, null=True, blank=True)),
+                (
+                    "vcs_type",
+                    models.CharField(
+                        max_length=5, choices=[("svn", "Subversion"), ("git", "Git"), ("hg", "Mercurial")]
+                    ),
+                ),
+                ("vcs_root", models.CharField(max_length=200)),
+                ("vcs_web", models.URLField()),
+                (
+                    "ext_platform",
+                    models.URLField(help_text="URL to external translation platform, if any", null=True, 
blank=True),
+                ),
+                ("archived", models.BooleanField(default=False)),
+                (
+                    "maintainers",
+                    models.ManyToManyField(
+                        help_text="Automatically updated if the module contains a doap file.",
+                        related_name="maintains_modules",
+                        db_table="module_maintainer",
+                        to="people.Person",
+                        blank=True,
+                    ),
+                ),
             ],
             options={
-                'ordering': ('name',),
-                'db_table': 'module',
+                "ordering": ("name",),
+                "db_table": "module",
             },
         ),
         migrations.CreateModel(
-            name='PoFile',
+            name="PoFile",
             fields=[
-                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, 
primary_key=True)),
-                ('path', models.CharField(max_length=255, null=True, blank=True)),
-                ('updated', models.DateTimeField(auto_now_add=True)),
-                ('translated', models.IntegerField(default=0)),
-                ('fuzzy', models.IntegerField(default=0)),
-                ('untranslated', models.IntegerField(default=0)),
-                ('figures', common.fields.JSONField(null=True, blank=True)),
-                ('translated_words', models.IntegerField(default=0)),
-                ('fuzzy_words', models.IntegerField(default=0)),
-                ('untranslated_words', models.IntegerField(default=0)),
+                ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, 
primary_key=True)),
+                ("path", models.CharField(max_length=255, null=True, blank=True)),
+                ("updated", models.DateTimeField(auto_now_add=True)),
+                ("translated", models.IntegerField(default=0)),
+                ("fuzzy", models.IntegerField(default=0)),
+                ("untranslated", models.IntegerField(default=0)),
+                ("figures", common.fields.JSONField(null=True, blank=True)),
+                ("translated_words", models.IntegerField(default=0)),
+                ("fuzzy_words", models.IntegerField(default=0)),
+                ("untranslated_words", models.IntegerField(default=0)),
             ],
             options={
-                'db_table': 'pofile',
+                "db_table": "pofile",
             },
         ),
         migrations.CreateModel(
-            name='Release',
+            name="Release",
             fields=[
-                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, 
primary_key=True)),
-                ('name', models.SlugField(max_length=20)),
-                ('description', models.CharField(max_length=50)),
-                ('string_frozen', models.BooleanField(default=False)),
-                ('status', models.CharField(max_length=12, choices=[('official', 'Official'), ('unofficial', 
'Unofficial'), ('xternal', 'External')])),
-                ('weight', models.IntegerField(default=0)),
-                ('branches', models.ManyToManyField(related_name='releases', through='stats.Category', 
to='stats.Branch')),
+                ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, 
primary_key=True)),
+                ("name", models.SlugField(max_length=20)),
+                ("description", models.CharField(max_length=50)),
+                ("string_frozen", models.BooleanField(default=False)),
+                (
+                    "status",
+                    models.CharField(
+                        max_length=12,
+                        choices=[("official", "Official"), ("unofficial", "Unofficial"), ("xternal", 
"External")],
+                    ),
+                ),
+                ("weight", models.IntegerField(default=0)),
+                (
+                    "branches",
+                    models.ManyToManyField(related_name="releases", through="stats.Category", 
to="stats.Branch"),
+                ),
             ],
             options={
-                'ordering': ('status', '-name'),
-                'db_table': 'release',
+                "ordering": ("status", "-name"),
+                "db_table": "release",
             },
         ),
         migrations.CreateModel(
-            name='Statistics',
+            name="Statistics",
             fields=[
-                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, 
primary_key=True)),
-                ('branch', models.ForeignKey(to='stats.Branch', on_delete=models.CASCADE)),
-                ('domain', models.ForeignKey(to='stats.Domain', on_delete=models.CASCADE)),
-                ('full_po', models.OneToOneField(related_name='stat_f', null=True, 
on_delete=django.db.models.deletion.SET_NULL, to='stats.PoFile')),
-                ('language', models.ForeignKey(to='languages.Language', null=True, 
on_delete=models.CASCADE)),
-                ('part_po', models.OneToOneField(related_name='stat_p', null=True, 
on_delete=django.db.models.deletion.SET_NULL, to='stats.PoFile')),
+                ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, 
primary_key=True)),
+                ("branch", models.ForeignKey(to="stats.Branch", on_delete=models.CASCADE)),
+                ("domain", models.ForeignKey(to="stats.Domain", on_delete=models.CASCADE)),
+                (
+                    "full_po",
+                    models.OneToOneField(
+                        related_name="stat_f",
+                        null=True,
+                        on_delete=django.db.models.deletion.SET_NULL,
+                        to="stats.PoFile",
+                    ),
+                ),
+                ("language", models.ForeignKey(to="languages.Language", null=True, 
on_delete=models.CASCADE)),
+                (
+                    "part_po",
+                    models.OneToOneField(
+                        related_name="stat_p",
+                        null=True,
+                        on_delete=django.db.models.deletion.SET_NULL,
+                        to="stats.PoFile",
+                    ),
+                ),
             ],
             options={
-                'db_table': 'statistics',
-                'verbose_name': 'statistics',
-                'verbose_name_plural': 'statistics',
+                "db_table": "statistics",
+                "verbose_name": "statistics",
+                "verbose_name_plural": "statistics",
             },
         ),
         migrations.CreateModel(
-            name='StatisticsArchived',
+            name="StatisticsArchived",
             fields=[
-                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, 
primary_key=True)),
-                ('module', models.TextField()),
-                ('type', models.CharField(max_length=3, choices=[('ui', 'User Interface'), ('doc', 
'Documentation')])),
-                ('domain', models.TextField()),
-                ('branch', models.TextField()),
-                ('language', models.CharField(max_length=15)),
-                ('date', models.DateTimeField()),
-                ('translated', models.IntegerField(default=0)),
-                ('fuzzy', models.IntegerField(default=0)),
-                ('untranslated', models.IntegerField(default=0)),
+                ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, 
primary_key=True)),
+                ("module", models.TextField()),
+                ("type", models.CharField(max_length=3, choices=[("ui", "User Interface"), ("doc", 
"Documentation")])),
+                ("domain", models.TextField()),
+                ("branch", models.TextField()),
+                ("language", models.CharField(max_length=15)),
+                ("date", models.DateTimeField()),
+                ("translated", models.IntegerField(default=0)),
+                ("fuzzy", models.IntegerField(default=0)),
+                ("untranslated", models.IntegerField(default=0)),
             ],
             options={
-                'db_table': 'statistics_archived',
+                "db_table": "statistics_archived",
             },
         ),
         migrations.AddField(
-            model_name='informationarchived',
-            name='statistics',
-            field=models.ForeignKey(to='stats.StatisticsArchived', on_delete=models.CASCADE),
+            model_name="informationarchived",
+            name="statistics",
+            field=models.ForeignKey(to="stats.StatisticsArchived", on_delete=models.CASCADE),
         ),
         migrations.AddField(
-            model_name='information',
-            name='statistics',
-            field=models.ForeignKey(to='stats.Statistics', on_delete=models.CASCADE),
+            model_name="information",
+            name="statistics",
+            field=models.ForeignKey(to="stats.Statistics", on_delete=models.CASCADE),
         ),
         migrations.AddField(
-            model_name='domain',
-            name='module',
-            field=models.ForeignKey(to='stats.Module', on_delete=models.CASCADE),
+            model_name="domain",
+            name="module",
+            field=models.ForeignKey(to="stats.Module", on_delete=models.CASCADE),
         ),
         migrations.AddField(
-            model_name='category',
-            name='release',
-            field=models.ForeignKey(to='stats.Release', on_delete=models.CASCADE),
+            model_name="category",
+            name="release",
+            field=models.ForeignKey(to="stats.Release", on_delete=models.CASCADE),
         ),
         migrations.AddField(
-            model_name='branch',
-            name='module',
-            field=models.ForeignKey(to='stats.Module', on_delete=models.CASCADE),
+            model_name="branch",
+            name="module",
+            field=models.ForeignKey(to="stats.Module", on_delete=models.CASCADE),
         ),
         migrations.AlterUniqueTogether(
-            name='statistics',
-            unique_together=set([('branch', 'domain', 'language')]),
+            name="statistics",
+            unique_together=set([("branch", "domain", "language")]),
         ),
         migrations.AlterUniqueTogether(
-            name='category',
-            unique_together=set([('release', 'branch')]),
+            name="category",
+            unique_together=set([("release", "branch")]),
         ),
         migrations.AlterUniqueTogether(
-            name='branch',
-            unique_together=set([('name', 'module')]),
+            name="branch",
+            unique_together=set([("name", "module")]),
         ),
     ]
diff --git a/stats/migrations/0002_add_category_name.py b/stats/migrations/0002_add_category_name.py
index 2f8bed47..355ebbd8 100644
--- a/stats/migrations/0002_add_category_name.py
+++ b/stats/migrations/0002_add_category_name.py
@@ -1,27 +1,29 @@
-from django.db import models, migrations
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('stats', '0001_initial'),
+        ("stats", "0001_initial"),
     ]
 
     operations = [
         migrations.CreateModel(
-            name='CategoryName',
+            name="CategoryName",
             fields=[
-                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, 
primary_key=True)),
-                ('name', models.CharField(unique=True, max_length=30)),
+                ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, 
primary_key=True)),
+                ("name", models.CharField(unique=True, max_length=30)),
             ],
             options={
-                'db_table': 'categoryname',
+                "db_table": "categoryname",
             },
         ),
         migrations.AddField(
-            model_name='category',
-            name='name_id',
-            field=models.ForeignKey(db_column='name_id', on_delete=django.db.models.deletion.PROTECT, 
to='stats.CategoryName', null=True),
+            model_name="category",
+            name="name_id",
+            field=models.ForeignKey(
+                db_column="name_id", on_delete=django.db.models.deletion.PROTECT, to="stats.CategoryName", 
null=True
+            ),
         ),
     ]
diff --git a/stats/migrations/0003_migrate_category_names.py b/stats/migrations/0003_migrate_category_names.py
index 8e8f55e9..a4787a8f 100644
--- a/stats/migrations/0003_migrate_category_names.py
+++ b/stats/migrations/0003_migrate_category_names.py
@@ -1,23 +1,23 @@
 from django.db import migrations
 
 CATEGORY_CHOICES = (
-    ('default', 'Default'),
-    ('admin-tools', 'Administration Tools'),
-    ('dev-tools', 'Development Tools'),
-    ('desktop', 'GNOME Desktop'),
-    ('dev-platform', 'GNOME Developer Platform'),
-    ('proposed', 'New Module Proposals'),
-    ('g3-core', 'Core'),
-    ('g3-utils', 'Utils'),
-    ('g3-apps', 'Apps'),
-    ('g3-a11y', 'Accessibility'),
-    ('g3-games', 'Games'),
-    ('g3-backends', 'Backends'),
-    ('g3-core-libs', 'Core Libraries'),
-    ('g3-extra-libs', 'Extra Libraries'),
-    ('g2-legacy', 'Legacy Desktop'),
-    ('stable', 'Stable Branches'),
-    ('dev', 'Development Branches'),
+    ("default", "Default"),
+    ("admin-tools", "Administration Tools"),
+    ("dev-tools", "Development Tools"),
+    ("desktop", "GNOME Desktop"),
+    ("dev-platform", "GNOME Developer Platform"),
+    ("proposed", "New Module Proposals"),
+    ("g3-core", "Core"),
+    ("g3-utils", "Utils"),
+    ("g3-apps", "Apps"),
+    ("g3-a11y", "Accessibility"),
+    ("g3-games", "Games"),
+    ("g3-backends", "Backends"),
+    ("g3-core-libs", "Core Libraries"),
+    ("g3-extra-libs", "Extra Libraries"),
+    ("g2-legacy", "Legacy Desktop"),
+    ("stable", "Stable Branches"),
+    ("dev", "Development Branches"),
 )
 
 
@@ -28,14 +28,14 @@ def migrate_categs(apps, schema_editor):
     for cat_key, cat_name in CATEGORY_CHOICES:
         cn = CategoryName.objects.create(name=cat_name)
         Category.objects.filter(name=cat_key).update(name_id=cn)
-        if cat_name == 'Development Tools':
-            Category.objects.filter(name='g3-dev-tools').update(name_id=cn)
+        if cat_name == "Development Tools":
+            Category.objects.filter(name="g3-dev-tools").update(name_id=cn)
 
 
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('stats', '0002_add_category_name'),
+        ("stats", "0002_add_category_name"),
     ]
 
     operations = [
diff --git a/stats/migrations/0004_remove_old_cat_name.py b/stats/migrations/0004_remove_old_cat_name.py
index ec8e296f..6007606f 100644
--- a/stats/migrations/0004_remove_old_cat_name.py
+++ b/stats/migrations/0004_remove_old_cat_name.py
@@ -1,26 +1,26 @@
-from django.db import models, migrations
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('stats', '0003_migrate_category_names'),
+        ("stats", "0003_migrate_category_names"),
     ]
 
     operations = [
         migrations.RemoveField(
-            model_name='category',
-            name='name',
+            model_name="category",
+            name="name",
         ),
         migrations.RenameField(
-            model_name='category',
-            old_name='name_id',
-            new_name='name',
+            model_name="category",
+            old_name="name_id",
+            new_name="name",
         ),
         migrations.AlterField(
-            model_name='category',
-            name='name',
-            field=models.ForeignKey(to='stats.CategoryName', on_delete=django.db.models.deletion.PROTECT),
+            model_name="category",
+            name="name",
+            field=models.ForeignKey(to="stats.CategoryName", on_delete=django.db.models.deletion.PROTECT),
         ),
     ]
diff --git a/stats/migrations/0005_update_module_name_field.py 
b/stats/migrations/0005_update_module_name_field.py
index 19234fa2..40439ad7 100644
--- a/stats/migrations/0005_update_module_name_field.py
+++ b/stats/migrations/0005_update_module_name_field.py
@@ -1,18 +1,29 @@
-from django.db import models, migrations
 import re
+
 import django.core.validators
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('stats', '0004_remove_old_cat_name'),
+        ("stats", "0004_remove_old_cat_name"),
     ]
 
     operations = [
         migrations.AlterField(
-            model_name='module',
-            name='name',
-            field=models.CharField(unique=True, max_length=50, 
validators=[django.core.validators.RegexValidator(re.compile('^[-\\+a-zA-Z0-9_]+\\Z'), "Enter a valid 'slug' 
consisting of letters, numbers, underscores, hyphens or plus signs.", 'invalid')]),
+            model_name="module",
+            name="name",
+            field=models.CharField(
+                unique=True,
+                max_length=50,
+                validators=[
+                    django.core.validators.RegexValidator(
+                        re.compile("^[-\\+a-zA-Z0-9_]+\\Z"),
+                        "Enter a valid 'slug' consisting of letters, numbers, underscores, hyphens or plus 
signs.",
+                        "invalid",
+                    )
+                ],
+            ),
         ),
     ]
diff --git a/stats/migrations/0006_add_domain_branch_from_to.py 
b/stats/migrations/0006_add_domain_branch_from_to.py
index 05f6ece9..5d62e056 100644
--- a/stats/migrations/0006_add_domain_branch_from_to.py
+++ b/stats/migrations/0006_add_domain_branch_from_to.py
@@ -1,27 +1,36 @@
-from django.db import models, migrations
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('stats', '0005_update_module_name_field'),
+        ("stats", "0005_update_module_name_field"),
     ]
 
     operations = [
         migrations.AddField(
-            model_name='domain',
-            name='branch_from',
-            field=models.ForeignKey(related_name='+', on_delete=django.db.models.deletion.PROTECT, 
blank=True, to='stats.Branch', null=True),
+            model_name="domain",
+            name="branch_from",
+            field=models.ForeignKey(
+                related_name="+", on_delete=django.db.models.deletion.PROTECT, blank=True, 
to="stats.Branch", null=True
+            ),
         ),
         migrations.AddField(
-            model_name='domain',
-            name='branch_to',
-            field=models.ForeignKey(related_name='+', on_delete=django.db.models.deletion.PROTECT, 
blank=True, to='stats.Branch', null=True),
+            model_name="domain",
+            name="branch_to",
+            field=models.ForeignKey(
+                related_name="+", on_delete=django.db.models.deletion.PROTECT, blank=True, 
to="stats.Branch", null=True
+            ),
         ),
         migrations.AlterField(
-            model_name='domain',
-            name='pot_method',
-            field=models.CharField(help_text="Leave blank for standard method (intltool for UI and 
gnome-doc-utils for DOC), or '&lt;gettext&gt;' for the pure xgettext-based extraction", max_length=100, 
null=True, blank=True),
+            model_name="domain",
+            name="pot_method",
+            field=models.CharField(
+                help_text="Leave blank for standard method (intltool for UI and gnome-doc-utils for DOC), or 
'&lt;gettext&gt;' for the pure xgettext-based extraction",
+                max_length=100,
+                null=True,
+                blank=True,
+            ),
         ),
     ]
diff --git a/stats/migrations/0007_extend_bugs_base.py b/stats/migrations/0007_extend_bugs_base.py
index b233d829..a462bddb 100644
--- a/stats/migrations/0007_extend_bugs_base.py
+++ b/stats/migrations/0007_extend_bugs_base.py
@@ -4,13 +4,13 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('stats', '0006_add_domain_branch_from_to'),
+        ("stats", "0006_add_domain_branch_from_to"),
     ]
 
     operations = [
         migrations.AlterField(
-            model_name='module',
-            name='bugs_base',
+            model_name="module",
+            name="bugs_base",
             field=models.CharField(max_length=250, null=True, blank=True),
         ),
     ]
diff --git a/stats/migrations/0008_domain_extra_its_dirs.py b/stats/migrations/0008_domain_extra_its_dirs.py
index a04bf14c..9085ca0c 100644
--- a/stats/migrations/0008_domain_extra_its_dirs.py
+++ b/stats/migrations/0008_domain_extra_its_dirs.py
@@ -4,13 +4,15 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('stats', '0007_extend_bugs_base'),
+        ("stats", "0007_extend_bugs_base"),
     ]
 
     operations = [
         migrations.AddField(
-            model_name='domain',
-            name='extra_its_dirs',
-            field=models.TextField(help_text='colon-separated directories containing extra .its/.loc files 
for gettext', blank=True),
+            model_name="domain",
+            name="extra_its_dirs",
+            field=models.TextField(
+                help_text="colon-separated directories containing extra .its/.loc files for gettext", 
blank=True
+            ),
         ),
     ]
diff --git a/stats/migrations/0009_remove_null_on_text_fields.py 
b/stats/migrations/0009_remove_null_on_text_fields.py
index 5e8740e2..a0968b84 100644
--- a/stats/migrations/0009_remove_null_on_text_fields.py
+++ b/stats/migrations/0009_remove_null_on_text_fields.py
@@ -4,86 +4,102 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('stats', '0008_domain_extra_its_dirs'),
+        ("stats", "0008_domain_extra_its_dirs"),
     ]
 
     operations = [
         migrations.AlterField(
-            model_name='branch',
-            name='vcs_subpath',
-            field=models.CharField(blank=True, default='', max_length=50),
+            model_name="branch",
+            name="vcs_subpath",
+            field=models.CharField(blank=True, default="", max_length=50),
             preserve_default=False,
         ),
         migrations.AlterField(
-            model_name='domain',
-            name='description',
-            field=models.TextField(blank=True, default=''),
+            model_name="domain",
+            name="description",
+            field=models.TextField(blank=True, default=""),
             preserve_default=False,
         ),
         migrations.AlterField(
-            model_name='domain',
-            name='linguas_location',
-            field=models.CharField(blank=True, default='', help_text="Use 'no' for no LINGUAS check, or 
path/to/file#variable for a non-standard location.\nLeave blank for standard location (ALL_LINGUAS in 
LINGUAS/configure.ac/.in for UI and DOC_LINGUAS in Makefile.am for DOC)", max_length=50),
+            model_name="domain",
+            name="linguas_location",
+            field=models.CharField(
+                blank=True,
+                default="",
+                help_text="Use 'no' for no LINGUAS check, or path/to/file#variable for a non-standard 
location.\nLeave blank for standard location (ALL_LINGUAS in LINGUAS/configure.ac/.in for UI and DOC_LINGUAS 
in Makefile.am for DOC)",
+                max_length=50,
+            ),
             preserve_default=False,
         ),
         migrations.AlterField(
-            model_name='domain',
-            name='pot_method',
-            field=models.CharField(blank=True, default='', help_text="Leave blank for autodetected method 
(intltool/gettext/gnome-doc-utils), or '&lt;gettext&gt;', '&lt;intltool&gt;', or real pot command to force 
the tool chain", max_length=100),
+            model_name="domain",
+            name="pot_method",
+            field=models.CharField(
+                blank=True,
+                default="",
+                help_text="Leave blank for autodetected method (intltool/gettext/gnome-doc-utils), or 
'&lt;gettext&gt;', '&lt;intltool&gt;', or real pot command to force the tool chain",
+                max_length=100,
+            ),
             preserve_default=False,
         ),
         migrations.AlterField(
-            model_name='domain',
-            name='red_filter',
-            field=models.TextField(blank=True, default='', help_text='pogrep filter to strip po file from 
unprioritized strings (format: location|string, “-” for no filter)'),
+            model_name="domain",
+            name="red_filter",
+            field=models.TextField(
+                blank=True,
+                default="",
+                help_text="pogrep filter to strip po file from unprioritized strings (format: 
location|string, “-” for no filter)",
+            ),
             preserve_default=False,
         ),
         migrations.AlterField(
-            model_name='module',
-            name='bugs_base',
-            field=models.CharField(blank=True, default='', max_length=250),
+            model_name="module",
+            name="bugs_base",
+            field=models.CharField(blank=True, default="", max_length=250),
             preserve_default=False,
         ),
         migrations.AlterField(
-            model_name='module',
-            name='bugs_component',
-            field=models.CharField(blank=True, default='', max_length=50),
+            model_name="module",
+            name="bugs_component",
+            field=models.CharField(blank=True, default="", max_length=50),
             preserve_default=False,
         ),
         migrations.AlterField(
-            model_name='module',
-            name='bugs_product',
-            field=models.CharField(blank=True, default='', max_length=50),
+            model_name="module",
+            name="bugs_product",
+            field=models.CharField(blank=True, default="", max_length=50),
             preserve_default=False,
         ),
         migrations.AlterField(
-            model_name='module',
-            name='comment',
-            field=models.TextField(blank=True, default=''),
+            model_name="module",
+            name="comment",
+            field=models.TextField(blank=True, default=""),
             preserve_default=False,
         ),
         migrations.AlterField(
-            model_name='module',
-            name='description',
-            field=models.TextField(blank=True, default=''),
+            model_name="module",
+            name="description",
+            field=models.TextField(blank=True, default=""),
             preserve_default=False,
         ),
         migrations.AlterField(
-            model_name='module',
-            name='ext_platform',
-            field=models.URLField(blank=True, default='', help_text='URL to external translation platform, 
if any'),
+            model_name="module",
+            name="ext_platform",
+            field=models.URLField(blank=True, default="", help_text="URL to external translation platform, 
if any"),
             preserve_default=False,
         ),
         migrations.AlterField(
-            model_name='module',
-            name='homepage',
-            field=models.URLField(blank=True, default='', help_text='Automatically updated if the module 
contains a doap file.'),
+            model_name="module",
+            name="homepage",
+            field=models.URLField(
+                blank=True, default="", help_text="Automatically updated if the module contains a doap file."
+            ),
             preserve_default=False,
         ),
         migrations.AlterField(
-            model_name='pofile',
-            name='path',
-            field=models.CharField(blank=True, default='', max_length=255),
+            model_name="pofile",
+            name="path",
+            field=models.CharField(blank=True, default="", max_length=255),
             preserve_default=False,
         ),
     ]
diff --git a/stats/migrations/0010_pot_method.py b/stats/migrations/0010_pot_method.py
index 2e05f01a..14b73f4f 100644
--- a/stats/migrations/0010_pot_method.py
+++ b/stats/migrations/0010_pot_method.py
@@ -4,23 +4,39 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('stats', '0009_remove_null_on_text_fields'),
+        ("stats", "0009_remove_null_on_text_fields"),
     ]
 
     operations = [
         migrations.RenameField(
-            model_name='domain',
-            old_name='pot_method',
-            new_name='pot_method_old',
+            model_name="domain",
+            old_name="pot_method",
+            new_name="pot_method_old",
         ),
         migrations.AddField(
-            model_name='domain',
-            name='pot_method',
-            field=models.CharField(choices=[('auto', 'auto detected'), ('gettext', 'gettext'), ('intltool', 
'intltool'), ('shell', 'shell command'), ('url', 'URL'), ('in_repo', '.pot in repository'), ('subtitles', 
'subtitles')], default='auto', max_length=20),
+            model_name="domain",
+            name="pot_method",
+            field=models.CharField(
+                choices=[
+                    ("auto", "auto detected"),
+                    ("gettext", "gettext"),
+                    ("intltool", "intltool"),
+                    ("shell", "shell command"),
+                    ("url", "URL"),
+                    ("in_repo", ".pot in repository"),
+                    ("subtitles", "subtitles"),
+                ],
+                default="auto",
+                max_length=20,
+            ),
         ),
         migrations.AddField(
-            model_name='domain',
-            name='pot_params',
-            field=models.CharField(blank=True, help_text="pot_method='url': URL, pot_method='shell': shell 
command, pot_method='gettext': optional params", max_length=100),
+            model_name="domain",
+            name="pot_params",
+            field=models.CharField(
+                blank=True,
+                help_text="pot_method='url': URL, pot_method='shell': shell command, pot_method='gettext': 
optional params",
+                max_length=100,
+            ),
         ),
     ]
diff --git a/stats/migrations/0011_migrate_pot_method.py b/stats/migrations/0011_migrate_pot_method.py
index e424e079..fd06fe78 100644
--- a/stats/migrations/0011_migrate_pot_method.py
+++ b/stats/migrations/0011_migrate_pot_method.py
@@ -3,18 +3,18 @@ from django.db import migrations
 
 def migrate_pot_method(apps, schema_editor):
     Domain = apps.get_model("stats", "Domain")
-    for domain in Domain.objects.exclude(pot_method_old=''):
-        if domain.pot_method_old == ':':
-            domain.pot_method = 'in_repo'
-        elif domain.pot_method_old == '<gettext>':
-            domain.pot_method = 'gettext'
-        elif domain.pot_method_old == '<intltool>':
-            domain.pot_method = 'intltool'
-        elif domain.pot_method_old.startswith('http'):
-            domain.pot_method = 'url'
+    for domain in Domain.objects.exclude(pot_method_old=""):
+        if domain.pot_method_old == ":":
+            domain.pot_method = "in_repo"
+        elif domain.pot_method_old == "<gettext>":
+            domain.pot_method = "gettext"
+        elif domain.pot_method_old == "<intltool>":
+            domain.pot_method = "intltool"
+        elif domain.pot_method_old.startswith("http"):
+            domain.pot_method = "url"
             domain.pot_params = domain.pot_method_old
         else:
-            domain.pot_method = 'shell'
+            domain.pot_method = "shell"
             domain.pot_params = domain.pot_method_old
         domain.save()
 
@@ -22,7 +22,7 @@ def migrate_pot_method(apps, schema_editor):
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('stats', '0010_pot_method'),
+        ("stats", "0010_pot_method"),
     ]
 
     operations = [migrations.RunPython(migrate_pot_method)]
diff --git a/stats/migrations/0012_remove_domain_pot_method_old.py 
b/stats/migrations/0012_remove_domain_pot_method_old.py
index 86f5d94a..427450d6 100644
--- a/stats/migrations/0012_remove_domain_pot_method_old.py
+++ b/stats/migrations/0012_remove_domain_pot_method_old.py
@@ -6,12 +6,12 @@ from django.db import migrations
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('stats', '0011_migrate_pot_method'),
+        ("stats", "0011_migrate_pot_method"),
     ]
 
     operations = [
         migrations.RemoveField(
-            model_name='domain',
-            name='pot_method_old',
+            model_name="domain",
+            name="pot_method_old",
         ),
     ]
diff --git a/stats/migrations/0013_domain_layout.py b/stats/migrations/0013_domain_layout.py
index cf07eeed..28fb5e8a 100644
--- a/stats/migrations/0013_domain_layout.py
+++ b/stats/migrations/0013_domain_layout.py
@@ -4,14 +4,18 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('stats', '0012_remove_domain_pot_method_old'),
+        ("stats", "0012_remove_domain_pot_method_old"),
     ]
 
     operations = [
         migrations.AddField(
-            model_name='domain',
-            name='layout',
-            field=models.CharField(default='', help_text="UI standard is 'po/{lang}.po', doc standard is 
'po/{lang}/{lang}.po'", max_length=100),
+            model_name="domain",
+            name="layout",
+            field=models.CharField(
+                default="",
+                help_text="UI standard is 'po/{lang}.po', doc standard is 'po/{lang}/{lang}.po'",
+                max_length=100,
+            ),
             preserve_default=False,
         ),
     ]
diff --git a/stats/migrations/0014_migrate_dir_to_layout.py b/stats/migrations/0014_migrate_dir_to_layout.py
index 002c8645..792c58d7 100644
--- a/stats/migrations/0014_migrate_dir_to_layout.py
+++ b/stats/migrations/0014_migrate_dir_to_layout.py
@@ -5,17 +5,17 @@ def migrate_layout(apps, schema_editor):
     Domain = apps.get_model("stats", "Domain")
     for dom in Domain.objects.all():
         # This rule is valid for 95% of cases. The rest will need manual tweak.
-        if dom.dtype == 'ui':
-            dom.layout = '%s/{lang}.po' % dom.directory
-        elif dom.dtype == 'doc':
-            dom.layout = '%s/{lang}/{lang}.po' % dom.directory
+        if dom.dtype == "ui":
+            dom.layout = "%s/{lang}.po" % dom.directory
+        elif dom.dtype == "doc":
+            dom.layout = "%s/{lang}/{lang}.po" % dom.directory
         dom.save()
 
 
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('stats', '0013_domain_layout'),
+        ("stats", "0013_domain_layout"),
     ]
 
     operations = [migrations.RunPython(migrate_layout, migrations.RunPython.noop)]
diff --git a/stats/migrations/0015_remove_domain_directory.py 
b/stats/migrations/0015_remove_domain_directory.py
index 79e23a1b..09d2676e 100644
--- a/stats/migrations/0015_remove_domain_directory.py
+++ b/stats/migrations/0015_remove_domain_directory.py
@@ -4,12 +4,12 @@ from django.db import migrations
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('stats', '0014_migrate_dir_to_layout'),
+        ("stats", "0014_migrate_dir_to_layout"),
     ]
 
     operations = [
         migrations.RemoveField(
-            model_name='domain',
-            name='directory',
+            model_name="domain",
+            name="directory",
         ),
     ]
diff --git a/stats/migrations/0016_removed_bugs_fields.py b/stats/migrations/0016_removed_bugs_fields.py
index 03a29708..6672e219 100644
--- a/stats/migrations/0016_removed_bugs_fields.py
+++ b/stats/migrations/0016_removed_bugs_fields.py
@@ -4,16 +4,16 @@ from django.db import migrations
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('stats', '0015_remove_domain_directory'),
+        ("stats", "0015_remove_domain_directory"),
     ]
 
     operations = [
         migrations.RemoveField(
-            model_name='module',
-            name='bugs_component',
+            model_name="module",
+            name="bugs_component",
         ),
         migrations.RemoveField(
-            model_name='module',
-            name='bugs_product',
+            model_name="module",
+            name="bugs_product",
         ),
     ]
diff --git a/stats/migrations/0017_pofile_path_relative.py b/stats/migrations/0017_pofile_path_relative.py
index 1a10aad3..84207ff2 100644
--- a/stats/migrations/0017_pofile_path_relative.py
+++ b/stats/migrations/0017_pofile_path_relative.py
@@ -1,4 +1,5 @@
 import os
+
 from django.conf import settings
 from django.db import migrations
 
@@ -9,22 +10,21 @@ def strip_path_prefix(apps, schema_editor):
 
     def strip_path(stat):
         old_path = stat.full_po.path
-        stat.full_po.path = old_path.split('/' + scratch_dir + '/')[1]
+        stat.full_po.path = old_path.split("/" + scratch_dir + "/")[1]
         stat.full_po.save()
-        if stat.part_po is not None and stat.part_po.path.startswith('/'):
+        if stat.part_po is not None and stat.part_po.path.startswith("/"):
             old_path = stat.part_po.path
-            stat.part_po.path = old_path.split('/' + scratch_dir + '/')[1]
+            stat.part_po.path = old_path.split("/" + scratch_dir + "/")[1]
             stat.part_po.save()
-        
-    
-    for stat in Statistics.objects.filter(full_po__isnull=False, full_po__path__startswith='/'):
+
+    for stat in Statistics.objects.filter(full_po__isnull=False, full_po__path__startswith="/"):
         strip_path(stat)
 
 
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('stats', '0016_removed_bugs_fields'),
+        ("stats", "0016_removed_bugs_fields"),
     ]
 
     operations = [migrations.RunPython(strip_path_prefix, migrations.RunPython.noop)]
diff --git a/stats/migrations/0018_new_jsonfields.py b/stats/migrations/0018_new_jsonfields.py
index a263addc..a142c95b 100644
--- a/stats/migrations/0018_new_jsonfields.py
+++ b/stats/migrations/0018_new_jsonfields.py
@@ -4,28 +4,28 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('stats', '0017_pofile_path_relative'),
+        ("stats", "0017_pofile_path_relative"),
     ]
 
     operations = [
         migrations.RenameField(
-            model_name='pofile',
-            old_name='figures',
-            new_name='figures_old',
+            model_name="pofile",
+            old_name="figures",
+            new_name="figures_old",
         ),
         migrations.RenameField(
-            model_name='branch',
-            old_name='file_hashes',
-            new_name='file_hashes_old',
+            model_name="branch",
+            old_name="file_hashes",
+            new_name="file_hashes_old",
         ),
         migrations.AddField(
-            model_name='pofile',
-            name='figures',
+            model_name="pofile",
+            name="figures",
             field=models.JSONField(blank=True, null=True),
         ),
         migrations.AddField(
-            model_name='branch',
-            name='file_hashes',
+            model_name="branch",
+            name="file_hashes",
             field=models.JSONField(blank=True, null=True, editable=False),
         ),
     ]
diff --git a/stats/migrations/0019_migrate_old_custom_files.py 
b/stats/migrations/0019_migrate_old_custom_files.py
index 79207d18..415752c3 100644
--- a/stats/migrations/0019_migrate_old_custom_files.py
+++ b/stats/migrations/0019_migrate_old_custom_files.py
@@ -7,7 +7,7 @@ def migrate_figures(apps, schema_editor):
     for file_ in PoFile.objects.exclude(figures_old__isnull=False):
         file_.figures = file_.figures_old
         file_.save()
-    for branch in Branch.objects.exclude(file_hashes_old=''):
+    for branch in Branch.objects.exclude(file_hashes_old=""):
         branch.file_hashes = branch.file_hashes_old
         branch.save()
 
@@ -15,7 +15,7 @@ def migrate_figures(apps, schema_editor):
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('stats', '0018_new_jsonfields'),
+        ("stats", "0018_new_jsonfields"),
     ]
 
     operations = [
diff --git a/stats/migrations/0020_remove_pofile_figures_old.py 
b/stats/migrations/0020_remove_pofile_figures_old.py
index d9edf09f..da6ee6b8 100644
--- a/stats/migrations/0020_remove_pofile_figures_old.py
+++ b/stats/migrations/0020_remove_pofile_figures_old.py
@@ -4,16 +4,16 @@ from django.db import migrations
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('stats', '0019_migrate_old_custom_files'),
+        ("stats", "0019_migrate_old_custom_files"),
     ]
 
     operations = [
         migrations.RemoveField(
-            model_name='pofile',
-            name='figures_old',
+            model_name="pofile",
+            name="figures_old",
         ),
         migrations.RemoveField(
-            model_name='branch',
-            name='file_hashes_old',
+            model_name="branch",
+            name="file_hashes_old",
         ),
     ]
diff --git a/stats/migrations/0021_release_name_unique.py b/stats/migrations/0021_release_name_unique.py
index 347d7cda..c12a3ca6 100644
--- a/stats/migrations/0021_release_name_unique.py
+++ b/stats/migrations/0021_release_name_unique.py
@@ -4,13 +4,13 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('stats', '0020_remove_pofile_figures_old'),
+        ("stats", "0020_remove_pofile_figures_old"),
     ]
 
     operations = [
         migrations.AlterField(
-            model_name='release',
-            name='name',
+            model_name="release",
+            name="name",
             field=models.SlugField(max_length=20, unique=True),
         ),
     ]
diff --git a/stats/models.py b/stats/models.py
index b7eefdb7..c5af2309 100644
--- a/stats/models.py
+++ b/stats/models.py
@@ -1,8 +1,8 @@
 import logging
 import os
-import sys
 import re
 import shutil
+import sys
 import threading
 from collections import Counter, OrderedDict
 from datetime import datetime
@@ -24,58 +24,56 @@ from django.urls import reverse
 from django.utils import dateformat, timezone
 from django.utils.encoding import force_str
 from django.utils.functional import cached_property
-from django.utils.translation import ngettext, gettext as _, gettext_noop
+from django.utils.translation import gettext as _
+from django.utils.translation import gettext_noop, ngettext
 
 from common.utils import is_site_admin, run_shell_command
+from languages.models import Language
+from people.models import Person
 from stats import repos, signals, utils
 from stats.doap import update_doap_infos
-from people.models import Person
-from languages.models import Language
 
 logger = logging.getLogger(__name__)
 
 # These args should be kept in sync with
 # https://github.com/mesonbuild/meson/blob/master/mesonbuild/modules/i18n.py#L25
 GLIB_PRESET = (
-    '--keyword=_',
-    '--keyword=N_',
-    '--keyword=C_:1c,2',
-    '--keyword=NC_:1c,2',
-    '--keyword=g_dcgettext:2',
-    '--keyword=g_dngettext:2,3',
-    '--keyword=g_dpgettext2:2c,3',
-
-    '--flag=N_:1:pass-c-format',
-    '--flag=C_:2:pass-c-format',
-    '--flag=NC_:2:pass-c-format',
-    '--flag=g_dngettext:2:pass-c-format',
-    '--flag=g_strdup_printf:1:c-format',
-    '--flag=g_string_printf:2:c-format',
-    '--flag=g_string_append_printf:2:c-format',
-    '--flag=g_error_new:3:c-format',
-    '--flag=g_set_error:4:c-format',
+    "--keyword=_",
+    "--keyword=N_",
+    "--keyword=C_:1c,2",
+    "--keyword=NC_:1c,2",
+    "--keyword=g_dcgettext:2",
+    "--keyword=g_dngettext:2,3",
+    "--keyword=g_dpgettext2:2c,3",
+    "--flag=N_:1:pass-c-format",
+    "--flag=C_:2:pass-c-format",
+    "--flag=NC_:2:pass-c-format",
+    "--flag=g_dngettext:2:pass-c-format",
+    "--flag=g_strdup_printf:1:c-format",
+    "--flag=g_string_printf:2:c-format",
+    "--flag=g_string_append_printf:2:c-format",
+    "--flag=g_error_new:3:c-format",
+    "--flag=g_set_error:4:c-format",
 )
 
 # Standard Django slug validation but also accept '+' (for gtk+)
-slug_re = re.compile(r'^[-\+a-zA-Z0-9_]+\Z')
+slug_re = re.compile(r"^[-\+a-zA-Z0-9_]+\Z")
 validate_slug = RegexValidator(
-    slug_re,
-    "Enter a valid 'slug' consisting of letters, numbers, underscores, hyphens or plus signs.",
-    'invalid'
+    slug_re, "Enter a valid 'slug' consisting of letters, numbers, underscores, hyphens or plus signs.", 
"invalid"
 )
 
 VCS_TYPE_CHOICES = (
-    ('svn', 'Subversion'),
-    ('git', 'Git'),
-    ('hg', 'Mercurial'),
+    ("svn", "Subversion"),
+    ("git", "Git"),
+    ("hg", "Mercurial"),
 )
 
 BRANCH_HEAD_NAMES = (
-    'HEAD',
-    'trunk',
-    'main',
-    'mainline',
-    'master',
+    "HEAD",
+    "trunk",
+    "main",
+    "mainline",
+    "master",
 )
 
 
@@ -85,10 +83,7 @@ class UnableToCommit(Exception):
 
 class Module(models.Model):
     name = models.CharField(max_length=50, unique=True, validators=[validate_slug])
-    homepage = models.URLField(
-        blank=True,
-        help_text="Automatically updated if the module contains a doap file."
-    )
+    homepage = models.URLField(blank=True, help_text="Automatically updated if the module contains a doap 
file.")
     description = models.TextField(blank=True)
     comment = models.TextField(blank=True)
     bugs_base = models.CharField(max_length=250, blank=True)
@@ -96,23 +91,20 @@ class Module(models.Model):
     # URLField is too restrictive for vcs_root
     vcs_root = models.CharField(max_length=200)
     vcs_web = models.URLField()
-    ext_platform = models.URLField(
-        blank=True,
-        help_text="URL to external translation platform, if any"
-    )
+    ext_platform = models.URLField(blank=True, help_text="URL to external translation platform, if any")
     archived = models.BooleanField(default=False)
 
     maintainers = models.ManyToManyField(
         Person,
-        db_table='module_maintainer',
-        related_name='maintains_modules',
+        db_table="module_maintainer",
+        related_name="maintains_modules",
         blank=True,
-        help_text="Automatically updated if the module contains a doap file."
+        help_text="Automatically updated if the module contains a doap file.",
     )
 
     class Meta:
-        db_table = 'module'
-        ordering = ('name',)
+        db_table = "module"
+        ordering = ("name",)
 
     def __str__(self):
         return self.name
@@ -121,11 +113,11 @@ class Module(models.Model):
         return self.name < other.name
 
     def get_absolute_url(self):
-        return reverse('module', args=[self.name])
+        return reverse("module", args=[self.name])
 
     def get_description(self):
         if self.description:
-            return '{name} — {description}'.format(name=self.name, description=_(self.description))
+            return "{name} — {description}".format(name=self.name, description=_(self.description))
         return self.name
 
     def get_comment(self):
@@ -137,41 +129,42 @@ class Module(models.Model):
                 comment,
                 _(
                     'Translations for this module are externally hosted. Please go to the <a 
href="%(link)s">'
-                    'external platform</a> to see how you can submit your translation.'
-                ) % {'link': self.ext_platform}
+                    "external platform</a> to see how you can submit your translation."
+                )
+                % {"link": self.ext_platform},
             )
         return comment
 
     def has_standard_vcs(self):
-        """ This function checks if the module is hosted in the standard VCS of the project """
+        """This function checks if the module is hosted in the standard VCS of the project"""
         return re.search(settings.VCS_HOME_REGEX, self.vcs_root) is not None
 
     def get_bugs_i18n_url(self, content=None):
-        if 'gitlab' in self.bugs_base:
-            link = utils.url_join(self.bugs_base, '?state=opened&label_name[]=8.%20Translation')
+        if "gitlab" in self.bugs_base:
+            link = utils.url_join(self.bugs_base, "?state=opened&label_name[]=8.%20Translation")
             if content:
-                link += "&search=%s" % content.replace(' ', '+')
+                link += "&search=%s" % content.replace(" ", "+")
             return link
         return None
 
     def get_bugs_enter_url(self):
-        if 'gitlab' in self.bugs_base:
-            return utils.url_join(self.bugs_base, 'new')
+        if "gitlab" in self.bugs_base:
+            return utils.url_join(self.bugs_base, "new")
         return self.bugs_base
 
     def get_branches(self, reverse=False):
-        """ Return module branches, in ascending order by default (descending order if reverse == True) """
+        """Return module branches, in ascending order by default (descending order if reverse == True)"""
         return sorted(self.branch_set.all(), reverse=reverse)
 
     def get_head_branch(self):
-        """ Returns the HEAD (trunk, master, ...) branch of the module """
+        """Returns the HEAD (trunk, master, ...) branch of the module"""
         branch = self.branch_set.filter(name__in=BRANCH_HEAD_NAMES).first()
         if not branch:
             branch = self.get_branches(reverse=False)[0]
         return branch
 
     def can_edit_branches(self, user):
-        """ Returns True for superusers, users with adequate permissions or maintainers of the module """
+        """Returns True for superusers, users with adequate permissions or maintainers of the module"""
         if is_site_admin(user) or user.username in [p.username for p in self.maintainers.all()]:
             return True
         return False
@@ -188,10 +181,11 @@ class Module(models.Model):
 
 
 class ModuleLock:
-    """ Weird things happen when multiple updates run in parallel for the same module
-        We use filesystem directories creation/deletion to act as global lock mecanism
+    """Weird things happen when multiple updates run in parallel for the same module
+    We use filesystem directories creation/deletion to act as global lock mecanism
     """
-    dir_prefix = 'updating-'
+
+    dir_prefix = "updating-"
 
     def __init__(self, mod):
         self.module = mod
@@ -234,7 +228,8 @@ class ModuleLock:
 
 @total_ordering
 class Branch(models.Model):
-    """ Branch of a module """
+    """Branch of a module"""
+
     name = models.CharField(max_length=50)
     vcs_subpath = models.CharField(max_length=50, blank=True)
     module = models.ForeignKey(Module, on_delete=models.CASCADE)
@@ -246,10 +241,10 @@ class Branch(models.Model):
     checkout_on_creation = True
 
     class Meta:
-        db_table = 'branch'
-        verbose_name_plural = 'branches'
-        ordering = ('name',)
-        unique_together = ('name', 'module')
+        db_table = "branch"
+        verbose_name_plural = "branches"
+        ordering = ("name",)
+        unique_together = ("name", "module")
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -262,7 +257,7 @@ class Branch(models.Model):
     @property
     def name_escaped(self):
         """Branch name suitable for including in file system paths."""
-        return self.name.replace('/', '_')
+        return self.name.replace("/", "_")
 
     @cached_property
     def _repo(self):
@@ -287,7 +282,7 @@ class Branch(models.Model):
                 self._repo.rename(old_name, self.name)
         if update_statistics and not self.module.archived:
             # The update command is launched asynchronously in a separate thread
-            upd_thread = threading.Thread(target=self.update_stats, kwargs={'force': True})
+            upd_thread = threading.Thread(target=self.update_stats, kwargs={"force": True})
             upd_thread.start()
 
     def delete(self):
@@ -298,8 +293,8 @@ class Branch(models.Model):
         # Remove the repo checkout
         self._repo.remove()
         # Remove the pot/po generated files
-        if os.access(str(self.output_dir('ui')), os.W_OK):
-            shutil.rmtree(str(self.output_dir('ui')))
+        if os.access(str(self.output_dir("ui")), os.W_OK):
+            shutil.rmtree(str(self.output_dir("ui")))
 
     def __hash__(self):
         return hash(self.pk)
@@ -319,11 +314,11 @@ class Branch(models.Model):
 
     @property
     def uses_meson(self):
-        return Path(self.co_path, 'meson.build').exists()
+        return Path(self.co_path, "meson.build").exists()
 
     @property
     def img_url_prefix(self):
-        return 'raw' if self.module.vcs_type == 'git' else ''
+        return "raw" if self.module.vcs_type == "git" else ""
 
     def is_head(self):
         return self.name in BRANCH_HEAD_NAMES
@@ -334,8 +329,8 @@ class Branch(models.Model):
         return ""
 
     def file_changed(self, rel_path):
-        """ This method determine if some file has changed based on its hash
-            Always returns true if this is the first time the path is checked
+        """This method determine if some file has changed based on its hash
+        Always returns true if this is the first time the path is checked
         """
         full_path = self.co_path / rel_path
         if not full_path.exists():
@@ -351,18 +346,18 @@ class Branch(models.Model):
         return True
 
     def has_string_frozen(self):
-        """ Returns true if the branch is contained in at least one string frozen release """
+        """Returns true if the branch is contained in at least one string frozen release"""
         return bool(self.releases.filter(string_frozen=True).count())
 
     def is_archive_only(self):
-        """ Return True if the branch only appears in 'archived' releases """
+        """Return True if the branch only appears in 'archived' releases"""
         return bool(self.releases.filter(weight__gt=0).count())
 
     def is_vcs_readonly(self):
-        return 'ssh://' not in self.module.vcs_root and not self.module.vcs_root.startswith('git@')
+        return "ssh://" not in self.module.vcs_root and not self.module.vcs_root.startswith("git@")
 
     def get_vcs_web_url(self):
-        if self.module.vcs_type in ('hg', 'git'):
+        if self.module.vcs_type in ("hg", "git"):
             return self.module.vcs_web
         if self.vcs_subpath:
             return utils.url_join(self.module.vcs_web, self.vcs_subpath)
@@ -371,16 +366,16 @@ class Branch(models.Model):
         return utils.url_join(self.module.vcs_web, "branches", self.name)
 
     def get_vcs_web_log_url(self):
-        """ Link to browsable commit log """
-        if self.module.vcs_type == 'git':
-            return utils.url_join(self.module.vcs_web, 'commits', self.name)
+        """Link to browsable commit log"""
+        if self.module.vcs_type == "git":
+            return utils.url_join(self.module.vcs_web, "commits", self.name)
         # Not implemented for other VCS
         return ""
 
     @cached_property
     def co_path(self):
-        """ Returns the path of the local checkout for the branch """
-        if self.module.vcs_type in ('hg', 'git'):
+        """Returns the path of the local checkout for the branch"""
+        if self.module.vcs_type in ("hg", "git"):
             branch_dir = self.module.name
         else:
             branch_dir = self.module.name + "." + self.name_escaped
@@ -391,7 +386,7 @@ class Branch(models.Model):
         Return all domains that this branch applies to.
         """
         domains = OrderedDict()
-        for dom in Domain.objects.filter(module=self.module).select_related('branch_from', 
'branch_to').all():
+        for dom in Domain.objects.filter(module=self.module).select_related("branch_from", 
"branch_to").all():
             if self.has_domain(dom):
                 domains[dom.name] = dom
         return domains
@@ -407,9 +402,9 @@ class Branch(models.Model):
         return self.co_path / domain.base_dir
 
     def output_dir(self, dom_type):
-        """ Directory where generated pot and po files are written on local system """
-        subdir = {'ui': '', 'doc': 'docs'}[dom_type]
-        dirname = settings.POTDIR / f'{self.module.name}.{self.name_escaped}' / subdir
+        """Directory where generated pot and po files are written on local system"""
+        subdir = {"ui": "", "doc": "docs"}[dom_type]
+        dirname = settings.POTDIR / f"{self.module.name}.{self.name_escaped}" / subdir
         dirname.mkdir(parents=True, exist_ok=True)
         return dirname
 
@@ -423,17 +418,15 @@ class Branch(models.Model):
         stats = OrderedDict()
         stats_langs = {}
         domain_pks = [d.pk for d in self.get_domains().values() if d.dtype == typ]
-        pot_stats = Statistics.objects.select_related(
-            "language", "domain", "branch", "full_po"
-        ).filter(
-            branch=self, language__isnull=True, domain__pk__in=domain_pks
-        ).order_by('domain__name')
+        pot_stats = (
+            Statistics.objects.select_related("language", "domain", "branch", "full_po")
+            .filter(branch=self, language__isnull=True, domain__pk__in=domain_pks)
+            .order_by("domain__name")
+        )
         for stat in pot_stats.all():
             stats[stat.domain.name] = [stat]
             stats_langs[stat.domain.name] = []
-        tr_stats = Statistics.objects.select_related(
-            "language", "domain", "branch", "full_po"
-        ).filter(
+        tr_stats = Statistics.objects.select_related("language", "domain", "branch", "full_po").filter(
             branch=self, language__isnull=False, domain__pk__in=domain_pks
         )
         for stat in tr_stats.all():
@@ -452,16 +445,16 @@ class Branch(models.Model):
 
     def get_doc_stats(self, mandatory_langs=()):
         if not self._doc_stats:
-            self._doc_stats = self.get_stats('doc', mandatory_langs)
+            self._doc_stats = self.get_stats("doc", mandatory_langs)
         return self._doc_stats
 
     def get_ui_stats(self, mandatory_langs=()):
         if not self._ui_stats:
-            self._ui_stats = self.get_stats('ui', mandatory_langs)
+            self._ui_stats = self.get_stats("ui", mandatory_langs)
         return self._ui_stats
 
     def update_stats(self, force, checkout=True, domain=None):
-        """ Update statistics for all po files from the branch """
+        """Update statistics for all po files from the branch"""
         with ModuleLock(self.module):
             checkout_errors = []
             if checkout:
@@ -487,8 +480,9 @@ class Branch(models.Model):
 
                 # 2. Pre-check, if available (e.g. intltool-update -m)
                 # **************************
-                if (dom.dtype == 'ui' and
-                        (dom.pot_method == 'auto' or (dom.pot_method == 'gettext' and dom.layout.count('/') 
< 2))):
+                if dom.dtype == "ui" and (
+                    dom.pot_method == "auto" or (dom.pot_method == "gettext" and dom.layout.count("/") < 2)
+                ):
                     # Run intltool-update -m to check for some errors
                     errors.extend(utils.check_potfiles(domain_path))
 
@@ -497,8 +491,8 @@ class Branch(models.Model):
                 potfile, errs = dom.generate_pot_file(self)
                 errors.extend(errs)
                 linguas = dom.get_linguas(self)
-                if linguas['langs'] is None and linguas['error']:
-                    errors.append(("warn", linguas['error']))
+                if linguas["langs"] is None and linguas["error"]:
+                    errors.append(("warn", linguas["error"]))
 
                 # Prepare statistics object
                 try:
@@ -511,15 +505,15 @@ class Branch(models.Model):
 
                 # 4. Compare with old pot files, various checks
                 # *****************************
-                previous_pot = self.output_dir(dom.dtype) / ('%s.%s.pot' % (dom.potbase(), 
self.name_escaped))
+                previous_pot = self.output_dir(dom.dtype) / ("%s.%s.pot" % (dom.potbase(), 
self.name_escaped))
                 if not potfile:
                     logger.error("Can’t generate POT file for %s/%s.", self.module.name, dom.name)
                     if previous_pot.exists():
                         # Use old POT file
                         potfile = previous_pot
-                        pot_stat.set_error('error', gettext_noop("Can’t generate POT file, using old one."))
+                        pot_stat.set_error("error", gettext_noop("Can’t generate POT file, using old one."))
                     else:
-                        pot_stat.set_error('error', gettext_noop("Can’t generate POT file, statistics 
aborted."))
+                        pot_stat.set_error("error", gettext_noop("Can’t generate POT file, statistics 
aborted."))
                         continue
 
                 # 5. Check if pot changed
@@ -529,7 +523,7 @@ class Branch(models.Model):
                 if previous_pot.exists():
                     # Compare old and new POT
                     changed_status, diff = utils.pot_diff_status(previous_pot, potfile)
-                    if string_frozen and dom.dtype == 'ui' and changed_status == 
utils.CHANGED_WITH_ADDITIONS:
+                    if string_frozen and dom.dtype == "ui" and changed_status == 
utils.CHANGED_WITH_ADDITIONS:
                         utils.notify_list(self, diff)
 
                 # 6. Generate pot stats and update DB
@@ -540,7 +534,7 @@ class Branch(models.Model):
                     try:
                         shutil.copyfile(str(potfile), str(previous_pot))
                     except Exception:  # FIXME: too broad exception
-                        pot_stat.set_error('error', gettext_noop("Can’t copy new POT file to public 
location."))
+                        pot_stat.set_error("error", gettext_noop("Can’t copy new POT file to public 
location."))
 
                 # Send pot_has_changed signal
                 if previous_pot.exists() and changed_status != utils.NOT_CHANGED:
@@ -549,24 +543,23 @@ class Branch(models.Model):
                 # 7. Update language po files and update DB
                 # *****************************************
                 stats_with_ext_errors = Statistics.objects.filter(
-                    branch=self, domain=dom, information__type__endswith='-ext'
+                    branch=self, domain=dom, information__type__endswith="-ext"
                 )
                 langs_with_ext_errors = [stat.language.locale for stat in stats_with_ext_errors]
                 dom_langs = dom.get_lang_files(self.co_path)
                 for lang, pofile in dom_langs:
-                    outpo = self.output_dir(dom.dtype) / (
-                        '%s.%s.%s.po' % (dom.potbase(), self.name_escaped, lang)
-                    )
-                    if (not force and changed_status in (utils.NOT_CHANGED, utils.CHANGED_ONLY_FORMATTING)
-                            and outpo.exists()
-                            and pofile.stat().st_mtime < outpo.stat().st_mtime
-                            and lang not in langs_with_ext_errors):
+                    outpo = self.output_dir(dom.dtype) / ("%s.%s.%s.po" % (dom.potbase(), self.name_escaped, 
lang))
+                    if (
+                        not force
+                        and changed_status in (utils.NOT_CHANGED, utils.CHANGED_ONLY_FORMATTING)
+                        and outpo.exists()
+                        and pofile.stat().st_mtime < outpo.stat().st_mtime
+                        and lang not in langs_with_ext_errors
+                    ):
                         continue
 
                     # msgmerge po with recent pot
-                    run_shell_command([
-                        'msgmerge', '--previous', '-o', str(outpo), str(pofile), str(potfile)
-                    ])
+                    run_shell_command(["msgmerge", "--previous", "-o", str(outpo), str(pofile), 
str(potfile)])
 
                     # Get Statistics object
                     try:
@@ -589,13 +582,11 @@ class Branch(models.Model):
                     if not errs:
                         stat.update_stats(outpo)
 
-                    if linguas['langs'] is not None and lang not in linguas['langs']:
-                        stat.set_error('warn-ext', linguas['error'])
+                    if linguas["langs"] is not None and lang not in linguas["langs"]:
+                        stat.set_error("warn-ext", linguas["error"])
 
                 # Delete stats for unexisting langs
-                Statistics.objects.filter(
-                    branch=self, domain=dom
-                ).exclude(
+                Statistics.objects.filter(branch=self, domain=dom).exclude(
                     models.Q(language__isnull=True) | models.Q(language__locale__in=[dl[0] for dl in 
dom_langs])
                 ).delete()
             # Check if doap file changed
@@ -603,7 +594,7 @@ class Branch(models.Model):
                 update_doap_infos(self.module)
 
     def checkout(self):
-        """ Do a checkout or an update of the VCS files """
+        """Do a checkout or an update of the VCS files"""
         logger.debug("Checking “%s.%s” out to “%s”…", self.module.name, self.name, self.co_path)
         self._repo.checkout()
 
@@ -617,7 +608,7 @@ class Branch(models.Model):
         self._repo.update()
 
     def commit_po(self, po_file, domain, language, author):
-        """ Commit the file 'po_file' in the branch VCS repository """
+        """Commit the file 'po_file' in the branch VCS repository"""
         with ModuleLock(self.module):
             self.update_repo()
             dest_full_path, existing, linguas_path = domain.commit_info(self, language)
@@ -661,73 +652,70 @@ class Branch(models.Model):
 
 
 class Domain(models.Model):
-    DOMAIN_TYPE_CHOICES = (
-        ('ui', 'User Interface'),
-        ('doc', 'Documentation')
-    )
+    DOMAIN_TYPE_CHOICES = (("ui", "User Interface"), ("doc", "Documentation"))
     POT_METHOD_CHOICES = (
-        ('auto', 'auto detected'),
-        ('gettext', 'gettext'),
-        ('intltool', 'intltool'),
-        ('shell', 'shell command'),
-        ('url', 'URL'),
-        ('in_repo', '.pot in repository'),
-        ('subtitles', 'subtitles'),
+        ("auto", "auto detected"),
+        ("gettext", "gettext"),
+        ("intltool", "intltool"),
+        ("shell", "shell command"),
+        ("url", "URL"),
+        ("in_repo", ".pot in repository"),
+        ("subtitles", "subtitles"),
     )
     module = models.ForeignKey(Module, on_delete=models.CASCADE)
     name = models.CharField(max_length=50)
     description = models.TextField(blank=True)
-    dtype = models.CharField(max_length=5, choices=DOMAIN_TYPE_CHOICES, default='ui')
+    dtype = models.CharField(max_length=5, choices=DOMAIN_TYPE_CHOICES, default="ui")
     layout = models.CharField(
-        max_length=100,
-        help_text="UI standard is 'po/{lang}.po', doc standard is 'po/{lang}/{lang}.po'"
+        max_length=100, help_text="UI standard is 'po/{lang}.po', doc standard is 'po/{lang}/{lang}.po'"
     )
-    pot_method = models.CharField(max_length=20, choices=POT_METHOD_CHOICES, default='auto')
+    pot_method = models.CharField(max_length=20, choices=POT_METHOD_CHOICES, default="auto")
     pot_params = models.CharField(
-        max_length=100, blank=True,
-        help_text="pot_method='url': URL, pot_method='shell': shell command, pot_method='gettext': optional 
params"
+        max_length=100,
+        blank=True,
+        help_text="pot_method='url': URL, pot_method='shell': shell command, pot_method='gettext': optional 
params",
     )
     extra_its_dirs = models.TextField(
-        blank=True,
-        help_text="colon-separated directories containing extra .its/.loc files for gettext"
+        blank=True, help_text="colon-separated directories containing extra .its/.loc files for gettext"
     )
     linguas_location = models.CharField(
-        max_length=50, blank=True,
+        max_length=50,
+        blank=True,
         help_text=(
             "Use 'no' for no LINGUAS check, or path/to/file#variable for a non-standard location.\n"
             "Leave blank for standard location (ALL_LINGUAS in LINGUAS/configure.ac/.in for UI and 
DOC_LINGUAS "
             "in Makefile.am for DOC)"
-        )
+        ),
     )
     red_filter = models.TextField(
         blank=True,
         help_text="pogrep filter to strip po file from unprioritized strings (format: location|string,"
-                  " “-” for no filter)"
+        " “-” for no filter)",
     )
     # Allow to specify the branches to which this domain applies
-    branch_from = models.ForeignKey(Branch, null=True, blank=True, on_delete=models.PROTECT, 
related_name='+')
-    branch_to = models.ForeignKey(Branch, null=True, blank=True, on_delete=models.PROTECT, related_name='+')
+    branch_from = models.ForeignKey(Branch, null=True, blank=True, on_delete=models.PROTECT, 
related_name="+")
+    branch_to = models.ForeignKey(Branch, null=True, blank=True, on_delete=models.PROTECT, related_name="+")
 
     class Meta:
-        db_table = 'domain'
-        ordering = ('-dtype', 'name')
+        db_table = "domain"
+        ordering = ("-dtype", "name")
 
     def __str__(self):
         return "%s (%s/%s)" % (self.name, self.module.name, self.get_dtype_display())
 
     @property
     def base_dir(self):
-        return self.layout[:self.layout.find('{lang}')]
+        return self.layout[: self.layout.find("{lang}")]
 
     def potbase(self):
         """
         Return the name of the generated pot file, without extension.
         (--default-domain for xgettext, --gettext-package for intltool)
         """
-        if self.name[:2] == 'po':
+        if self.name[:2] == "po":
             # e.g. replace po by gimp (for ui), or po-plugins by gimp-plugins
             return self.module.name + self.name[2:]
-        if self.name == 'help':
+        if self.name == "help":
             return "%s-help" % self.module.name
         return self.name
 
@@ -737,12 +725,13 @@ class Domain(models.Model):
         return self.potbase()
 
     def has_standard_location(self):
-        return (self.dtype == 'ui' and self.layout == 'po/{lang}.po') or (
-                self.dtype == 'doc' and self.layout == 'help/{lang}/{lang}.po')
+        return (self.dtype == "ui" and self.layout == "po/{lang}.po") or (
+            self.dtype == "doc" and self.layout == "help/{lang}/{lang}.po"
+        )
 
     def can_build_docs(self, branch):
         try:
-            return self.dtype == 'doc' and self.doc_format(branch)
+            return self.dtype == "doc" and self.doc_format(branch)
         except utils.UndetectableDocFormat:
             return False
 
@@ -768,15 +757,15 @@ class Domain(models.Model):
 
         def extract_lang(path):
             path_str = str(path)
-            start = len(str(base_path)) + self.layout.find('{lang}') + 1
-            return re.split(r'\.|/', path_str[start:])[0]
+            start = len(str(base_path)) + self.layout.find("{lang}") + 1
+            return re.split(r"\.|/", path_str[start:])[0]
 
-        for item in base_path.glob(self.layout.replace('{lang}', '*')):
+        for item in base_path.glob(self.layout.replace("{lang}", "*")):
             flist.append((extract_lang(item), item))
         return flist
 
     def generate_pot_file(self, current_branch):
-        """ Return the pot file generated (in the checkout tree), and the error if any """
+        """Return the pot file generated (in the checkout tree), and the error if any"""
 
         vcs_path = current_branch.co_path / self.base_dir
         env = None
@@ -784,10 +773,10 @@ class Domain(models.Model):
         pot_command = None
         errors = []
 
-        if self.pot_method == 'auto':
-            if self.dtype == 'ui':
+        if self.pot_method == "auto":
+            if self.dtype == "ui":
                 # Fallback is intltool, except for meson modules.
-                pot_method = 'gettext' if current_branch.uses_meson else 'intltool'
+                pot_method = "gettext" if current_branch.uses_meson else "intltool"
             else:
                 # Standard pot generation
                 potfile, errs = utils.generate_doc_pot_file(current_branch, self)
@@ -796,76 +785,76 @@ class Domain(models.Model):
         else:
             pot_method = self.pot_method
 
-        if self.module.name == 'damned-lies':
+        if self.module.name == "damned-lies":
             # Special case for d-l, pot file should be generated from running instance dir
-            call_command('update-trans', 'en', verbosity=0)
-            potfile = Path('./po') / 'damned-lies.pot'
+            call_command("update-trans", "en", verbosity=0)
+            potfile = Path("./po") / "damned-lies.pot"
 
-        elif pot_method == 'url':
+        elif pot_method == "url":
             # Get POT from URL and save file locally
             req = request.Request(self.pot_params)
             try:
                 handle = request.urlopen(req)
             except URLError:
                 return "", (("error", gettext_noop("Error retrieving pot file from URL.")),)
-            with potfile.open('wb') as f:
+            with potfile.open("wb") as f:
                 f.write(handle.read())
 
-        elif pot_method == 'gettext':
+        elif pot_method == "gettext":
             try:
                 pot_command, env = self.get_xgettext_command(current_branch)
             except Exception as err:
                 errors.append(str(err))
 
-        elif pot_method == 'intltool':
-            pot_command = ['intltool-update', '-g', self.potbase(), '-p']
+        elif pot_method == "intltool":
+            pot_command = ["intltool-update", "-g", self.potbase(), "-p"]
             if self.module.bugs_base:
-                env = {"XGETTEXT_ARGS": "\"--msgid-bugs-address=%s\"" % self.module.bugs_base}
+                env = {"XGETTEXT_ARGS": '"--msgid-bugs-address=%s"' % self.module.bugs_base}
 
-        elif pot_method == 'shell':
+        elif pot_method == "shell":
             pot_command = self.pot_params
 
-        elif pot_method == 'in_repo':
+        elif pot_method == "in_repo":
             pass  # Nothing to do, pot exists
 
-        elif pot_method == 'subtitles':
+        elif pot_method == "subtitles":
             from translate.convert import sub2po
-            srt_files = [
-                p for p in vcs_path.iterdir() if p.is_file() and p.name.endswith('.srt')
-            ]
+
+            srt_files = [p for p in vcs_path.iterdir() if p.is_file() and p.name.endswith(".srt")]
             if not srt_files:
                 # Try once more at parent level
-                srt_files = [
-                    p for p in vcs_path.parent.iterdir() if p.is_file() and p.name.endswith('.srt')
-                ]
+                srt_files = [p for p in vcs_path.parent.iterdir() if p.is_file() and p.name.endswith(".srt")]
                 if not srt_files:
                     return "", (("error", gettext_noop("No subtitle files found.")),)
-            with srt_files[0].open(mode='r') as po_fh, potfile.open(mode='wb') as pot_fh:
+            with srt_files[0].open(mode="r") as po_fh, potfile.open(mode="wb") as pot_fh:
                 sub2po.convertsub(po_fh, pot_fh)
 
         if pot_command:
             status, output, errs = run_shell_command(pot_command, env=env, cwd=vcs_path)
             if status != utils.STATUS_OK:
                 return "", (
-                    ("error",
-                     gettext_noop("Error regenerating POT file for 
%(file)s:\n<pre>%(cmd)s\n%(output)s</pre>") % {
-                         'file': self.potbase(),
-                         'cmd': ' '.join(pot_command) if isinstance(pot_command, list) else pot_command,
-                         'output': force_str(errs)
-                     }),
+                    (
+                        "error",
+                        gettext_noop("Error regenerating POT file for 
%(file)s:\n<pre>%(cmd)s\n%(output)s</pre>")
+                        % {
+                            "file": self.potbase(),
+                            "cmd": " ".join(pot_command) if isinstance(pot_command, list) else pot_command,
+                            "output": force_str(errs),
+                        },
+                    ),
                 )
             if not potfile.exists():
                 # Try to get POT file from command output, with path relative to checkout root
-                m = re.search(r'([\w/-]*\.pot)', output)
+                m = re.search(r"([\w/-]*\.pot)", output)
                 if m:
                     potfile = current_branch.co_path / m.group(0)
                 else:
                     # Try to find .pot file in /po dir
                     for file_ in vcs_path.iterdir():
-                        if file_.match('*.pot'):
+                        if file_.match("*.pot"):
                             potfile = file_
                             break
-            elif pot_method == 'gettext':
+            elif pot_method == "gettext":
                 # Filter out strings NOT to be translated, typically icon file names.
                 utils.exclude_untrans_messages(potfile)
 
@@ -882,53 +871,51 @@ class Domain(models.Model):
         if self.pot_params:
             xgettext_args.extend(self.pot_params.split())
         for opt, value in [
-            ('--directory', str(branch.co_path)),
-            ('--from-code', 'utf-8'),
-            ('--add-comments', ''),
-            ('--output', f'{self.potbase()}.pot'),
+            ("--directory", str(branch.co_path)),
+            ("--from-code", "utf-8"),
+            ("--add-comments", ""),
+            ("--output", f"{self.potbase()}.pot"),
         ]:
             if opt not in xgettext_args:
-                xgettext_args.extend([opt, value] if value != '' else [opt])
-        if (vcs_path / 'POTFILES.in').exists():
-            xgettext_args = ['--files-from', 'POTFILES.in'] + xgettext_args
-        elif (vcs_path / 'POTFILES').exists():
-            xgettext_args = ['--files-from', 'POTFILES'] + xgettext_args
+                xgettext_args.extend([opt, value] if value != "" else [opt])
+        if (vcs_path / "POTFILES.in").exists():
+            xgettext_args = ["--files-from", "POTFILES.in"] + xgettext_args
+        elif (vcs_path / "POTFILES").exists():
+            xgettext_args = ["--files-from", "POTFILES"] + xgettext_args
         else:
             raise RuntimeError(f"No POTFILES file found in {self.base_dir}")
 
         if not os.path.exists(utils.ITS_DATA_DIR):
             utils.collect_its_data()
-        env = {'GETTEXTDATADIRS': os.path.dirname(utils.ITS_DATA_DIR)}
+        env = {"GETTEXTDATADIRS": os.path.dirname(utils.ITS_DATA_DIR)}
         if self.extra_its_dirs:
-            env['GETTEXTDATADIRS'] = ':'.join(
-                [env['GETTEXTDATADIRS']] + [str(branch.co_path / path)
-                                            for path in self.extra_its_dirs.split(':')]
+            env["GETTEXTDATADIRS"] = ":".join(
+                [env["GETTEXTDATADIRS"]] + [str(branch.co_path / path) for path in 
self.extra_its_dirs.split(":")]
             )
 
         # Parse and use content from: "XGETTEXT_OPTIONS = --keyword=_ --keyword=N_"
-        makefile = utils.MakefileWrapper.find_file(branch, [vcs_path], file_name='Makevars')
+        makefile = utils.MakefileWrapper.find_file(branch, [vcs_path], file_name="Makevars")
         if makefile:
-            kwds_vars = makefile.read_variable('XGETTEXT_OPTIONS')
+            kwds_vars = makefile.read_variable("XGETTEXT_OPTIONS")
             if kwds_vars:
                 xgettext_args.extend(kwds_vars.split())
         else:
-            makefile = utils.MakefileWrapper.find_file(branch, [vcs_path], file_name='meson.build')
+            makefile = utils.MakefileWrapper.find_file(branch, [vcs_path], file_name="meson.build")
             if makefile:
-                if makefile.read_variable('gettext.preset') == 'glib' or not makefile.readable:
+                if makefile.read_variable("gettext.preset") == "glib" or not makefile.readable:
                     xgettext_args.extend(GLIB_PRESET)
-                extra_args = makefile.read_variable('gettext.args')
+                extra_args = makefile.read_variable("gettext.args")
                 if extra_args:
                     xgettext_args.extend([extra_args] if isinstance(extra_args, str) else extra_args)
-                datadirs = makefile.read_variable('gettext.data_dirs')
+                datadirs = makefile.read_variable("gettext.data_dirs")
                 if datadirs:
-                    env['GETTEXTDATADIRS'] = ':'.join(
-                        [env['GETTEXTDATADIRS']] +
-                        [str(branch.co_path / path) for path in datadirs]
+                    env["GETTEXTDATADIRS"] = ":".join(
+                        [env["GETTEXTDATADIRS"]] + [str(branch.co_path / path) for path in datadirs]
                     )
         # Added last as some chars in it may disturb CLI parsing
         if self.module.bugs_base:
-            xgettext_args.extend(['--msgid-bugs-address', self.module.bugs_base])
-        return ['xgettext'] + xgettext_args, env
+            xgettext_args.extend(["--msgid-bugs-address", self.module.bugs_base])
+        return ["xgettext"] + xgettext_args, env
 
     def commit_info(self, branch, language):
         """
@@ -945,8 +932,8 @@ class Domain(models.Model):
         abs_po_path = branch.co_path / self.get_po_path(language.locale)
         existing = abs_po_path.exists()
         linguas = None
-        if not existing and self.linguas_location != 'no':
-            linguas = branch.co_path / self.base_dir / 'LINGUAS'
+        if not existing and self.linguas_location != "no":
+            linguas = branch.co_path / self.base_dir / "LINGUAS"
             if not linguas.exists():
                 raise UnableToCommit(
                     _("Sorry, adding new translations when the LINGUAS file is not known is not supported.")
@@ -954,13 +941,13 @@ class Domain(models.Model):
         return abs_po_path, existing, linguas
 
     def get_linguas(self, branch):
-        """ Return a linguas dict like this: {'langs':['lang1', lang2], 'error':"Error"} """
+        """Return a linguas dict like this: {'langs':['lang1', lang2], 'error':"Error"}"""
         base_path = branch.co_path
         if self.linguas_location:
             # Custom (or no) linguas location
-            if self.linguas_location == 'no':
-                return {'langs': None, 'error': ''}
-            elif self.linguas_location.split('/')[-1] == "LINGUAS":
+            if self.linguas_location == "no":
+                return {"langs": None, "error": ""}
+            elif self.linguas_location.split("/")[-1] == "LINGUAS":
                 return utils.read_linguas_file(base_path / self.linguas_location)
             else:
                 variable = "ALL_LINGUAS"
@@ -971,26 +958,21 @@ class Domain(models.Model):
                 linguas_file = utils.MakefileWrapper(branch, base_path / file_path)
                 langs = linguas_file.read_variable(variable)
                 return {
-                    'langs': langs.split() if langs is not None else None,
-                    'error': gettext_noop(
-                        "Entry for this language is not present in %(var)s variable in %(file)s file." % {
-                            'var': variable, 'file': file_path
-                        }
-                    )
+                    "langs": langs.split() if langs is not None else None,
+                    "error": gettext_noop(
+                        "Entry for this language is not present in %(var)s variable in %(file)s file."
+                        % {"var": variable, "file": file_path}
+                    ),
                 }
         # Standard linguas location
-        if self.dtype == 'ui':
+        if self.dtype == "ui":
             return utils.get_ui_linguas(branch, self.base_dir)
-        if self.dtype == 'doc':
+        if self.dtype == "doc":
             return utils.get_doc_linguas(branch, self.base_dir)
         raise ValueError("Domain dtype should be one of 'ui', 'doc'")
 
 
-RELEASE_STATUS_CHOICES = (
-    ('official', 'Official'),
-    ('unofficial', 'Unofficial'),
-    ('xternal', 'External')
-)
+RELEASE_STATUS_CHOICES = (("official", "Official"), ("unofficial", "Unofficial"), ("xternal", "External"))
 
 
 class Release(models.Model):
@@ -1000,11 +982,11 @@ class Release(models.Model):
     status = models.CharField(max_length=12, choices=RELEASE_STATUS_CHOICES)
     # weight is used to sort releases, higher on top, below 0 in archives
     weight = models.IntegerField(default=0)
-    branches = models.ManyToManyField(Branch, through='Category', related_name='releases')
+    branches = models.ManyToManyField(Branch, through="Category", related_name="releases")
 
     class Meta:
-        db_table = 'release'
-        ordering = ('status', '-name')
+        db_table = "release"
+        ordering = ("status", "-name")
 
     def __str__(self):
         return self.description
@@ -1016,97 +998,98 @@ class Release(models.Model):
     def excluded_domains(self):
         # Compute domains which doesn't apply for this release due to limited domain
         # (by branch_from/branch_to).
-        limited_stats = Statistics.objects.select_related(
-            'branch', 'domain'
-        ).filter(
-            branch__releases=self
-        ).filter(
-            language__isnull=True
-        ).filter(models.Q(domain__branch_from__isnull=False) | models.Q(domain__branch_to__isnull=False))
+        limited_stats = (
+            Statistics.objects.select_related("branch", "domain")
+            .filter(branch__releases=self)
+            .filter(language__isnull=True)
+            .filter(models.Q(domain__branch_from__isnull=False) | models.Q(domain__branch_to__isnull=False))
+        )
         return {st.domain for st in limited_stats if not st.branch.has_domain(st.domain)}
 
     @classmethod
     def total_by_releases(cls, dtype, releases):
-        """ Get summary stats for all languages and 'releases', and return a 'stats' dict with
-            each language locale as the key:
-            stats{
-              'll': {'lang': <language object>,
-                     'stats': [percentage for release 1, percentage for release 2, ...],
-                     'diff': difference in % between first and last release,
-                    }
-              'll': ...
-            }
+        """Get summary stats for all languages and 'releases', and return a 'stats' dict with
+        each language locale as the key:
+        stats{
+          'll': {'lang': <language object>,
+                 'stats': [percentage for release 1, percentage for release 2, ...],
+                 'diff': difference in % between first and last release,
+                }
+          'll': ...
+        }
         """
         stats = {}
         totals = [0] * len(releases)
         lang_dict = dict((lang.locale, lang) for lang in Language.objects.all())
         for rel in releases:
-            query = Statistics.objects.filter(
-                domain__dtype=dtype, branch__releases=rel
-            ).exclude(
-                domain__in=rel.excluded_domains
-            ).values(
-                'language__locale'
-            ).annotate(
-                trans=models.Sum('full_po__translated'), fuzzy=models.Sum('full_po__fuzzy'),
-                untrans=models.Sum('full_po__untranslated')
-            ).order_by('language__name')
+            query = (
+                Statistics.objects.filter(domain__dtype=dtype, branch__releases=rel)
+                .exclude(domain__in=rel.excluded_domains)
+                .values("language__locale")
+                .annotate(
+                    trans=models.Sum("full_po__translated"),
+                    fuzzy=models.Sum("full_po__fuzzy"),
+                    untrans=models.Sum("full_po__untranslated"),
+                )
+                .order_by("language__name")
+            )
             for line in query:
-                locale = line['language__locale']
+                locale = line["language__locale"]
                 if locale and locale not in stats:
-                    stats[locale] = {
-                        'lang': lang_dict[locale],
-                        'stats': [0] * len(releases)
-                    }
+                    stats[locale] = {"lang": lang_dict[locale], "stats": [0] * len(releases)}
                 if locale is None:  # POT stats
-                    totals[releases.index(rel)] = line['untrans']
+                    totals[releases.index(rel)] = line["untrans"]
                 else:
-                    stats[locale]['stats'][releases.index(rel)] = line['trans']
+                    stats[locale]["stats"][releases.index(rel)] = line["trans"]
 
         # Compute percentages
         def compute_percentage(x, y):
             return int(x / y * 100)
 
         for k in stats:
-            stats[k]['stats'] = list(map(compute_percentage, stats[k]['stats'], totals))
-            stats[k]['diff'] = stats[k]['stats'][-1] - stats[k]['stats'][0]
+            stats[k]["stats"] = list(map(compute_percentage, stats[k]["stats"], totals))
+            stats[k]["diff"] = stats[k]["stats"][-1] - stats[k]["stats"][0]
         return stats
 
     def total_strings(self):
-        """ Returns the total number of strings in the release as a tuple (doc_total, ui_total) """
+        """Returns the total number of strings in the release as a tuple (doc_total, ui_total)"""
         # Use pot stats to compute total sum
-        qs = Statistics.objects.filter(
-            branch__category__release=self, language__isnull=True
-        ).exclude(
-            domain__in=self.excluded_domains
-        ).values(
-            'domain__dtype'
-        ).annotate(untrans=models.Sum('full_po__untranslated'))
+        qs = (
+            Statistics.objects.filter(branch__category__release=self, language__isnull=True)
+            .exclude(domain__in=self.excluded_domains)
+            .values("domain__dtype")
+            .annotate(untrans=models.Sum("full_po__untranslated"))
+        )
         totals = Counter()
         for line in qs:
-            totals[line['domain__dtype']] += line['untrans']
-        return totals['doc'], totals['ui']
+            totals[line["domain__dtype"]] += line["untrans"]
+        return totals["doc"], totals["ui"]
 
     def total_part_for_all_langs(self):
-        """ Return total partial UI strings for each language """
+        """Return total partial UI strings for each language"""
         total_part_ui_strings = {}
-        all_ui_pots = Statistics.objects.select_related(
-            'part_po'
-        ).exclude(
-            domain__in=self.excluded_domains
-        ).filter(language__isnull=True, branch__releases=self, domain__dtype='ui')
-        all_ui_stats = Statistics.objects.select_related(
-            'part_po', 'language'
-        ).exclude(
-            domain__in=self.excluded_domains
-        ).filter(
-            language__isnull=False, branch__releases=self, domain__dtype='ui'
-        ).values('branch_id', 'domain_id', 'language__locale', 'part_po__translated',
-                 'part_po__fuzzy', 'part_po__untranslated')
+        all_ui_pots = (
+            Statistics.objects.select_related("part_po")
+            .exclude(domain__in=self.excluded_domains)
+            .filter(language__isnull=True, branch__releases=self, domain__dtype="ui")
+        )
+        all_ui_stats = (
+            Statistics.objects.select_related("part_po", "language")
+            .exclude(domain__in=self.excluded_domains)
+            .filter(language__isnull=False, branch__releases=self, domain__dtype="ui")
+            .values(
+                "branch_id",
+                "domain_id",
+                "language__locale",
+                "part_po__translated",
+                "part_po__fuzzy",
+                "part_po__untranslated",
+            )
+        )
         stats_d = {
-            f"{st['branch_id']}-{st['domain_id']}-{st['language__locale']}": sum([
-                st['part_po__translated'] or 0, st['part_po__fuzzy'] or 0, st['part_po__untranslated'] or 0
-            ])
+            f"{st['branch_id']}-{st['domain_id']}-{st['language__locale']}": sum(
+                [st["part_po__translated"] or 0, st["part_po__fuzzy"] or 0, st["part_po__untranslated"] or 0]
+            )
             for st in all_ui_stats
         }
         for lang in Language.objects.all():
@@ -1114,29 +1097,34 @@ class Release(models.Model):
         return total_part_ui_strings
 
     def total_part_for_lang(self, lang, all_pots=None, all_stats_d=None):
-        """ For partial UI stats, the total number can differ from lang to lang, so we
-            are bound to iterate each stats to sum it """
+        """For partial UI stats, the total number can differ from lang to lang, so we
+        are bound to iterate each stats to sum it"""
         if all_pots is None:
-            all_pots = Statistics.objects.select_related(
-                'part_po'
-            ).exclude(
-                domain__in=self.excluded_domains
-            ).filter(language__isnull=True, branch__releases=self, domain__dtype='ui')
+            all_pots = (
+                Statistics.objects.select_related("part_po")
+                .exclude(domain__in=self.excluded_domains)
+                .filter(language__isnull=True, branch__releases=self, domain__dtype="ui")
+            )
         if all_stats_d is None:
-            all_stats = Statistics.objects.select_related(
-                'part_po', 'language'
-            ).exclude(
-                domain__in=self.excluded_domains
-            ).filter(
-                language=lang, branch__releases=self, domain__dtype='ui'
-            ).values(
-                'branch_id', 'domain_id', 'language__locale', 'part_po__translated',
-                'part_po__fuzzy', 'part_po__untranslated'
+            all_stats = (
+                Statistics.objects.select_related("part_po", "language")
+                .exclude(domain__in=self.excluded_domains)
+                .filter(language=lang, branch__releases=self, domain__dtype="ui")
+                .values(
+                    "branch_id",
+                    "domain_id",
+                    "language__locale",
+                    "part_po__translated",
+                    "part_po__fuzzy",
+                    "part_po__untranslated",
+                )
             )
             all_stats_d = {
-                "%d-%d-%s" % (st['branch_id'], st['domain_id'], st['language__locale']): sum(filter(
-                    None, [st['part_po__translated'], st['part_po__fuzzy'], st['part_po__untranslated']]
-                )) for st in all_stats
+                "%d-%d-%s"
+                % (st["branch_id"], st["domain_id"], st["language__locale"]): sum(
+                    filter(None, [st["part_po__translated"], st["part_po__fuzzy"], 
st["part_po__untranslated"]])
+                )
+                for st in all_stats
             }
         total = 0
         for stat in all_pots:
@@ -1145,164 +1133,192 @@ class Release(models.Model):
         return total
 
     def total_for_lang(self, lang):
-        """ Returns total translated/fuzzy/untranslated strings for a specific
-            language """
+        """Returns total translated/fuzzy/untranslated strings for a specific
+        language"""
 
         total_doc, total_ui = self.total_strings()
         total_ui_part = self.total_part_for_lang(lang)
-        query = Statistics.objects.filter(
-            language=lang, branch__releases=self
-        ).exclude(
-            domain__in=self.excluded_domains
-        ).values(
-            'domain__dtype'
-        ).annotate(
-            trans=Coalesce(models.Sum('full_po__translated'), models.Value(0)),
-            fuzzy=Coalesce(models.Sum('full_po__fuzzy'), models.Value(0)),
-            trans_p=Coalesce(models.Sum('part_po__translated'), models.Value(0)),
-            fuzzy_p=Coalesce(models.Sum('part_po__fuzzy'), models.Value(0))
+        query = (
+            Statistics.objects.filter(language=lang, branch__releases=self)
+            .exclude(domain__in=self.excluded_domains)
+            .values("domain__dtype")
+            .annotate(
+                trans=Coalesce(models.Sum("full_po__translated"), models.Value(0)),
+                fuzzy=Coalesce(models.Sum("full_po__fuzzy"), models.Value(0)),
+                trans_p=Coalesce(models.Sum("part_po__translated"), models.Value(0)),
+                fuzzy_p=Coalesce(models.Sum("part_po__fuzzy"), models.Value(0)),
+            )
         )
         stats = {
-            'id': self.id, 'name': self.name, 'description': _(self.description),
-            'ui': {
-                'translated': 0, 'fuzzy': 0, 'total': total_ui,
-                'translated_perc': 0, 'fuzzy_perc': 0, 'untranslated_perc': 0,
+            "id": self.id,
+            "name": self.name,
+            "description": _(self.description),
+            "ui": {
+                "translated": 0,
+                "fuzzy": 0,
+                "total": total_ui,
+                "translated_perc": 0,
+                "fuzzy_perc": 0,
+                "untranslated_perc": 0,
             },
-            'ui_part': {
-                'translated': 0, 'fuzzy': 0, 'total': total_ui_part,
-                'translated_perc': 0, 'fuzzy_perc': 0, 'untranslated_perc': 0,
+            "ui_part": {
+                "translated": 0,
+                "fuzzy": 0,
+                "total": total_ui_part,
+                "translated_perc": 0,
+                "fuzzy_perc": 0,
+                "untranslated_perc": 0,
             },
-            'doc': {
-                'translated': 0, 'fuzzy': 0, 'total': total_doc,
-                'translated_perc': 0, 'fuzzy_perc': 0, 'untranslated_perc': 0
+            "doc": {
+                "translated": 0,
+                "fuzzy": 0,
+                "total": total_doc,
+                "translated_perc": 0,
+                "fuzzy_perc": 0,
+                "untranslated_perc": 0,
             },
         }
         for res in query:
-            if res['domain__dtype'] == 'ui':
-                stats['ui']['translated'] = res['trans']
-                stats['ui']['fuzzy'] = res['fuzzy']
-                stats['ui_part']['translated'] = res['trans_p']
-                stats['ui_part']['fuzzy'] = res['fuzzy_p']
-            if res['domain__dtype'] == 'doc':
-                stats['doc']['translated'] = res['trans']
-                stats['doc']['fuzzy'] = res['fuzzy']
-        stats['ui']['untranslated'] = total_ui - (stats['ui']['translated'] + stats['ui']['fuzzy'])
-        stats['ui_part']['untranslated'] = total_ui_part - (stats['ui_part']['translated'] + 
stats['ui_part']['fuzzy'])
+            if res["domain__dtype"] == "ui":
+                stats["ui"]["translated"] = res["trans"]
+                stats["ui"]["fuzzy"] = res["fuzzy"]
+                stats["ui_part"]["translated"] = res["trans_p"]
+                stats["ui_part"]["fuzzy"] = res["fuzzy_p"]
+            if res["domain__dtype"] == "doc":
+                stats["doc"]["translated"] = res["trans"]
+                stats["doc"]["fuzzy"] = res["fuzzy"]
+        stats["ui"]["untranslated"] = total_ui - (stats["ui"]["translated"] + stats["ui"]["fuzzy"])
+        stats["ui_part"]["untranslated"] = total_ui_part - (stats["ui_part"]["translated"] + 
stats["ui_part"]["fuzzy"])
         if total_ui > 0:
-            stats['ui']['translated_perc'] = int(100 * stats['ui']['translated'] / total_ui)
-            stats['ui']['fuzzy_perc'] = int(100 * stats['ui']['fuzzy'] / total_ui)
-            stats['ui']['untranslated_perc'] = int(100 * stats['ui']['untranslated'] / total_ui)
+            stats["ui"]["translated_perc"] = int(100 * stats["ui"]["translated"] / total_ui)
+            stats["ui"]["fuzzy_perc"] = int(100 * stats["ui"]["fuzzy"] / total_ui)
+            stats["ui"]["untranslated_perc"] = int(100 * stats["ui"]["untranslated"] / total_ui)
         if total_ui_part > 0:
-            stats['ui_part']['translated_perc'] = int(100 * stats['ui_part']['translated'] / total_ui_part)
-            stats['ui_part']['fuzzy_perc'] = int(100 * stats['ui_part']['fuzzy'] / total_ui_part)
-            stats['ui_part']['untranslated_perc'] = int(100 * stats['ui_part']['untranslated'] / 
total_ui_part)
-        stats['doc']['untranslated'] = total_doc - (stats['doc']['translated'] + stats['doc']['fuzzy'])
+            stats["ui_part"]["translated_perc"] = int(100 * stats["ui_part"]["translated"] / total_ui_part)
+            stats["ui_part"]["fuzzy_perc"] = int(100 * stats["ui_part"]["fuzzy"] / total_ui_part)
+            stats["ui_part"]["untranslated_perc"] = int(100 * stats["ui_part"]["untranslated"] / 
total_ui_part)
+        stats["doc"]["untranslated"] = total_doc - (stats["doc"]["translated"] + stats["doc"]["fuzzy"])
         if total_doc > 0:
-            stats['doc']['translated_perc'] = int(100 * stats['doc']['translated'] / total_doc)
-            stats['doc']['fuzzy_perc'] = int(100 * stats['doc']['fuzzy'] / total_doc)
-            stats['doc']['untranslated_perc'] = int(100 * stats['doc']['untranslated'] / total_doc)
+            stats["doc"]["translated_perc"] = int(100 * stats["doc"]["translated"] / total_doc)
+            stats["doc"]["fuzzy_perc"] = int(100 * stats["doc"]["fuzzy"] / total_doc)
+            stats["doc"]["untranslated_perc"] = int(100 * stats["doc"]["untranslated"] / total_doc)
         return stats
 
     def get_global_stats(self):
-        """ Get statistics for all languages in a release, grouped by language
-            Returns a sorted list: (language name and locale, ui, ui-part and doc stats dictionaries) """
-
-        query = Statistics.objects.filter(
-            language__isnull=False, branch__releases=self
-        ).exclude(
-            domain__in=self.excluded_domains
-        ).values(
-            'domain__dtype', 'language'
-        ).annotate(
-            trans=Coalesce(models.Sum('full_po__translated'), models.Value(0)),
-            fuzzy=Coalesce(models.Sum('full_po__fuzzy'), models.Value(0)),
-            trans_p=Coalesce(models.Sum('part_po__translated'), models.Value(0)),
-            fuzzy_p=Coalesce(models.Sum('part_po__fuzzy'), models.Value(0)),
-            locale=models.Min('language__locale'), lang_name=models.Min('language__name')
-        ).order_by('domain__dtype', 'trans')
+        """Get statistics for all languages in a release, grouped by language
+        Returns a sorted list: (language name and locale, ui, ui-part and doc stats dictionaries)"""
+
+        query = (
+            Statistics.objects.filter(language__isnull=False, branch__releases=self)
+            .exclude(domain__in=self.excluded_domains)
+            .values("domain__dtype", "language")
+            .annotate(
+                trans=Coalesce(models.Sum("full_po__translated"), models.Value(0)),
+                fuzzy=Coalesce(models.Sum("full_po__fuzzy"), models.Value(0)),
+                trans_p=Coalesce(models.Sum("part_po__translated"), models.Value(0)),
+                fuzzy_p=Coalesce(models.Sum("part_po__fuzzy"), models.Value(0)),
+                locale=models.Min("language__locale"),
+                lang_name=models.Min("language__name"),
+            )
+            .order_by("domain__dtype", "trans")
+        )
         stats = {}
         total_docstrings, total_uistrings = self.total_strings()
         total_uistrings_part = self.total_part_for_all_langs()
         for row in query:
-            locale = row['locale']
+            locale = row["locale"]
             if locale not in stats:
                 # Initialize stats dict
                 stats[locale] = {
-                    'lang_name': row['lang_name'], 'lang_locale': locale,
-                    'ui': {
-                        'translated': 0, 'fuzzy': 0, 'untranslated': total_uistrings,
-                        'translated_perc': 0, 'fuzzy_perc': 0, 'untranslated_perc': 100
+                    "lang_name": row["lang_name"],
+                    "lang_locale": locale,
+                    "ui": {
+                        "translated": 0,
+                        "fuzzy": 0,
+                        "untranslated": total_uistrings,
+                        "translated_perc": 0,
+                        "fuzzy_perc": 0,
+                        "untranslated_perc": 100,
                     },
-                    'ui_part': {
-                        'translated': 0, 'fuzzy': 0, 'untranslated': total_uistrings_part[locale],
-                        'translated_perc': 0, 'fuzzy_perc': 0, 'untranslated_perc': 100
+                    "ui_part": {
+                        "translated": 0,
+                        "fuzzy": 0,
+                        "untranslated": total_uistrings_part[locale],
+                        "translated_perc": 0,
+                        "fuzzy_perc": 0,
+                        "untranslated_perc": 100,
                     },
-                    'doc': {
-                        'translated': 0, 'fuzzy': 0, 'untranslated': total_docstrings,
-                        'translated_perc': 0, 'fuzzy_perc': 0, 'untranslated_perc': 100
+                    "doc": {
+                        "translated": 0,
+                        "fuzzy": 0,
+                        "untranslated": total_docstrings,
+                        "translated_perc": 0,
+                        "fuzzy_perc": 0,
+                        "untranslated_perc": 100,
                     },
                 }
-            if row['domain__dtype'] == 'doc':
-                stats[locale]['doc']['translated'] = row['trans']
-                stats[locale]['doc']['fuzzy'] = row['fuzzy']
-                stats[locale]['doc']['untranslated'] = total_docstrings - (row['trans'] + row['fuzzy'])
+            if row["domain__dtype"] == "doc":
+                stats[locale]["doc"]["translated"] = row["trans"]
+                stats[locale]["doc"]["fuzzy"] = row["fuzzy"]
+                stats[locale]["doc"]["untranslated"] = total_docstrings - (row["trans"] + row["fuzzy"])
                 if total_docstrings > 0:
-                    stats[locale]['doc']['translated_perc'] = int(100 * row['trans'] / total_docstrings)
-                    stats[locale]['doc']['fuzzy_perc'] = int(100 * row['fuzzy'] / total_docstrings)
-                    stats[locale]['doc']['untranslated_perc'] = int(
-                        100 * stats[locale]['doc']['untranslated'] / total_docstrings
+                    stats[locale]["doc"]["translated_perc"] = int(100 * row["trans"] / total_docstrings)
+                    stats[locale]["doc"]["fuzzy_perc"] = int(100 * row["fuzzy"] / total_docstrings)
+                    stats[locale]["doc"]["untranslated_perc"] = int(
+                        100 * stats[locale]["doc"]["untranslated"] / total_docstrings
                     )
-            if row['domain__dtype'] == 'ui':
-                stats[locale]['ui']['translated'] = row['trans']
-                stats[locale]['ui']['fuzzy'] = row['fuzzy']
-                stats[locale]['ui']['untranslated'] = total_uistrings - (row['trans'] + row['fuzzy'])
-                stats[locale]['ui_part']['translated'] = row['trans_p']
-                stats[locale]['ui_part']['fuzzy'] = row['fuzzy_p']
-                stats[locale]['ui_part']['untranslated'] = total_uistrings_part[locale] - (
-                    row['trans_p'] + row['fuzzy_p']
+            if row["domain__dtype"] == "ui":
+                stats[locale]["ui"]["translated"] = row["trans"]
+                stats[locale]["ui"]["fuzzy"] = row["fuzzy"]
+                stats[locale]["ui"]["untranslated"] = total_uistrings - (row["trans"] + row["fuzzy"])
+                stats[locale]["ui_part"]["translated"] = row["trans_p"]
+                stats[locale]["ui_part"]["fuzzy"] = row["fuzzy_p"]
+                stats[locale]["ui_part"]["untranslated"] = total_uistrings_part[locale] - (
+                    row["trans_p"] + row["fuzzy_p"]
                 )
                 if total_uistrings > 0:
-                    stats[locale]['ui']['translated_perc'] = int(100 * row['trans'] / total_uistrings)
-                    stats[locale]['ui']['fuzzy_perc'] = int(100 * row['fuzzy'] / total_uistrings)
-                    stats[locale]['ui']['untranslated_perc'] = int(
-                        100 * stats[locale]['ui']['untranslated'] / total_uistrings)
-                if total_uistrings_part.get(locale, 0) > 0:
-                    stats[locale]['ui_part']['translated_perc'] = int(
-                        100 * row['trans_p'] / total_uistrings_part[locale]
+                    stats[locale]["ui"]["translated_perc"] = int(100 * row["trans"] / total_uistrings)
+                    stats[locale]["ui"]["fuzzy_perc"] = int(100 * row["fuzzy"] / total_uistrings)
+                    stats[locale]["ui"]["untranslated_perc"] = int(
+                        100 * stats[locale]["ui"]["untranslated"] / total_uistrings
                     )
-                    stats[locale]['ui_part']['fuzzy_perc'] = int(
-                        100 * row['fuzzy_p'] / total_uistrings_part[locale]
+                if total_uistrings_part.get(locale, 0) > 0:
+                    stats[locale]["ui_part"]["translated_perc"] = int(
+                        100 * row["trans_p"] / total_uistrings_part[locale]
                     )
-                    stats[locale]['ui_part']['untranslated_perc'] = int(
-                        100 * stats[locale]['ui_part']['untranslated'] / total_uistrings_part[locale]
+                    stats[locale]["ui_part"]["fuzzy_perc"] = int(100 * row["fuzzy_p"] / 
total_uistrings_part[locale])
+                    stats[locale]["ui_part"]["untranslated_perc"] = int(
+                        100 * stats[locale]["ui_part"]["untranslated"] / total_uistrings_part[locale]
                     )
 
         results = list(stats.values())
         # Sort by most translated first
-        results.sort(key=lambda st: (-st['ui']['translated'], -st['doc']['translated'], st['lang_name']))
+        results.sort(key=lambda st: (-st["ui"]["translated"], -st["doc"]["translated"], st["lang_name"]))
         return results
 
     def get_lang_stats(self, lang):
-        """ Get statistics for a specific language, producing the stats data structure
-            Used for displaying the language-release template """
+        """Get statistics for a specific language, producing the stats data structure
+        Used for displaying the language-release template"""
 
         stats = {
-            'doc': Statistics.get_lang_stats_by_type(lang, 'doc', self),
-            'ui': Statistics.get_lang_stats_by_type(lang, 'ui', self),
+            "doc": Statistics.get_lang_stats_by_type(lang, "doc", self),
+            "ui": Statistics.get_lang_stats_by_type(lang, "ui", self),
         }
         return stats
 
     def get_lang_files(self, lang, dtype):
-        """ Return a list of all po files of a lang for this release, preceded by the more recent 
modification date
-            It uses the POT file if there is no po for a module """
+        """Return a list of all po files of a lang for this release, preceded by the more recent 
modification date
+        It uses the POT file if there is no po for a module"""
         partial = False
         if dtype == "ui-part":
             dtype, partial = "ui", True
-        pot_stats = Statistics.objects.exclude(
-            domain__in=self.excluded_domains
-        ).filter(language=None, branch__releases=self, domain__dtype=dtype, full_po__isnull=False)
-        po_stats = {f"{st.branch_id}-{st.domain_id}": st
-                    for st in Statistics.objects.filter(language=lang, branch__releases=self, 
domain__dtype=dtype)}
+        pot_stats = Statistics.objects.exclude(domain__in=self.excluded_domains).filter(
+            language=None, branch__releases=self, domain__dtype=dtype, full_po__isnull=False
+        )
+        po_stats = {
+            f"{st.branch_id}-{st.domain_id}": st
+            for st in Statistics.objects.filter(language=lang, branch__releases=self, domain__dtype=dtype)
+        }
         lang_files = []
         last_modif_date = datetime(1970, 1, 1, tzinfo=timezone.utc)
         # Create list of files
@@ -1321,7 +1337,7 @@ class CategoryName(models.Model):
     name = models.CharField(max_length=30, unique=True)
 
     class Meta:
-        db_table = 'categoryname'
+        db_table = "categoryname"
 
     def __str__(self):
         return self.name
@@ -1333,9 +1349,9 @@ class Category(models.Model):
     name = models.ForeignKey(CategoryName, on_delete=models.PROTECT)
 
     class Meta:
-        db_table = 'category'
-        verbose_name_plural = 'categories'
-        unique_together = ('release', 'branch')
+        db_table = "category"
+        verbose_name_plural = "categories"
+        unique_together = ("release", "branch")
 
     def __str__(self):
         return "%s (%s, %s)" % (self.name, self.release, self.branch)
@@ -1356,7 +1372,7 @@ class PoFile(models.Model):
     untranslated_words = models.IntegerField(default=0)
 
     class Meta:
-        db_table = 'pofile'
+        db_table = "pofile"
 
     def __str__(self):
         return "%s (%s/%s/%s)" % (self.path, self.translated, self.fuzzy, self.untranslated)
@@ -1371,7 +1387,7 @@ class PoFile(models.Model):
 
     @property
     def full_path(self):
-        return self.prefix / self.path.lstrip('/')
+        return self.prefix / self.path.lstrip("/")
 
     def filename(self):
         return os.path.basename(self.path)
@@ -1382,7 +1398,7 @@ class PoFile(models.Model):
         return self.translated + self.fuzzy + self.untranslated
 
     def fig_count(self):
-        """ If stat of a document type, get the number of figures in the document """
+        """If stat of a document type, get the number of figures in the document"""
         return len(self.figures) if self.figures else 0
 
     def tr_percentage(self):
@@ -1423,13 +1439,13 @@ class PoFile(models.Model):
 
     def update_stats(self):
         stats = utils.po_file_stats(Path(self.full_path))
-        self.translated = stats['translated']
-        self.fuzzy = stats['fuzzy']
-        self.untranslated = stats['untranslated']
+        self.translated = stats["translated"]
+        self.fuzzy = stats["fuzzy"]
+        self.untranslated = stats["untranslated"]
 
-        self.translated_words = stats['translated_words']
-        self.fuzzy_words = stats['fuzzy_words']
-        self.untranslated_words = stats['untranslated_words']
+        self.translated_words = stats["translated_words"]
+        self.fuzzy_words = stats["fuzzy_words"]
+        self.untranslated_words = stats["untranslated_words"]
 
         self.save()
 
@@ -1439,14 +1455,14 @@ class Statistics(models.Model):
     domain = models.ForeignKey(Domain, on_delete=models.CASCADE)
     language = models.ForeignKey(Language, null=True, on_delete=models.CASCADE)
 
-    full_po = models.OneToOneField(PoFile, null=True, related_name='stat_f', on_delete=models.SET_NULL)
-    part_po = models.OneToOneField(PoFile, null=True, related_name='stat_p', on_delete=models.SET_NULL)
+    full_po = models.OneToOneField(PoFile, null=True, related_name="stat_f", on_delete=models.SET_NULL)
+    part_po = models.OneToOneField(PoFile, null=True, related_name="stat_p", on_delete=models.SET_NULL)
 
     class Meta:
-        db_table = 'statistics'
+        db_table = "statistics"
         verbose_name = "statistics"
         verbose_name_plural = verbose_name
-        unique_together = ('branch', 'domain', 'language')
+        unique_together = ("branch", "domain", "language")
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -1454,27 +1470,32 @@ class Statistics(models.Model):
         self.info_list = []
 
     def __str__(self):
-        """ String representation of the object """
-        return "%s (%s-%s) %s (%s)" % (self.branch.module.name, self.domain.dtype, self.domain.name,
-                                       self.branch.name, self.get_lang())
+        """String representation of the object"""
+        return "%s (%s-%s) %s (%s)" % (
+            self.branch.module.name,
+            self.domain.dtype,
+            self.domain.name,
+            self.branch.name,
+            self.get_lang(),
+        )
 
-    def translated(self, scope='full'):
-        return getattr(self.part_po if scope == 'part' else self.full_po, 'translated', 0)
+    def translated(self, scope="full"):
+        return getattr(self.part_po if scope == "part" else self.full_po, "translated", 0)
 
-    def translated_words(self, scope='full'):
-        return getattr(self.part_po if scope == 'part' else self.full_po, 'translated_words', 0)
+    def translated_words(self, scope="full"):
+        return getattr(self.part_po if scope == "part" else self.full_po, "translated_words", 0)
 
-    def fuzzy(self, scope='full'):
-        return getattr(self.part_po if scope == 'part' else self.full_po, 'fuzzy', 0)
+    def fuzzy(self, scope="full"):
+        return getattr(self.part_po if scope == "part" else self.full_po, "fuzzy", 0)
 
-    def fuzzy_words(self, scope='full'):
-        return getattr(self.part_po if scope == 'part' else self.full_po, 'fuzzy_words', 0)
+    def fuzzy_words(self, scope="full"):
+        return getattr(self.part_po if scope == "part" else self.full_po, "fuzzy_words", 0)
 
-    def untranslated(self, scope='full'):
-        return getattr(self.part_po if scope == 'part' else self.full_po, 'untranslated', 0)
+    def untranslated(self, scope="full"):
+        return getattr(self.part_po if scope == "part" else self.full_po, "untranslated", 0)
 
-    def untranslated_words(self, scope='full'):
-        return getattr(self.part_po if scope == 'part' else self.full_po, 'untranslated_words', 0)
+    def untranslated_words(self, scope="full"):
+        return getattr(self.part_po if scope == "part" else self.full_po, "untranslated_words", 0)
 
     def is_fake(self):
         return False
@@ -1483,58 +1504,58 @@ class Statistics(models.Model):
         return self.language_id is None
 
     def get_type(self):
-        """ Returns the type of the domain (ui, docbook, mallard) """
+        """Returns the type of the domain (ui, docbook, mallard)"""
         if self.domain.dtype == "ui":
             return "ui"
         return self.domain.doc_format(self.branch).format
 
-    def tr_percentage(self, scope='full'):
-        if scope == 'full' and self.full_po:
+    def tr_percentage(self, scope="full"):
+        if scope == "full" and self.full_po:
             return self.full_po.tr_percentage()
-        if scope == 'part' and self.part_po:
+        if scope == "part" and self.part_po:
             return self.part_po.tr_percentage()
         return 0
 
-    def tr_word_percentage(self, scope='full'):
-        if scope == 'full' and self.full_po:
+    def tr_word_percentage(self, scope="full"):
+        if scope == "full" and self.full_po:
             return self.full_po.tr_word_percentage()
-        if scope == 'part' and self.part_po:
+        if scope == "part" and self.part_po:
             return self.part_po.tr_word_percentage()
         return 0
 
-    def fu_percentage(self, scope='full'):
-        if scope == 'full' and self.full_po:
+    def fu_percentage(self, scope="full"):
+        if scope == "full" and self.full_po:
             return self.full_po.fu_percentage()
-        if scope == 'part' and self.part_po:
+        if scope == "part" and self.part_po:
             return self.part_po.fu_percentage()
         return 0
 
-    def fu_word_percentage(self, scope='full'):
-        if scope == 'full' and self.full_po:
+    def fu_word_percentage(self, scope="full"):
+        if scope == "full" and self.full_po:
             return self.full_po.fu_word_percentage()
-        if scope == 'part' and self.part_po:
+        if scope == "part" and self.part_po:
             return self.part_po.fu_word_percentage()
         return 0
 
-    def un_percentage(self, scope='full'):
-        if scope == 'full' and self.full_po:
+    def un_percentage(self, scope="full"):
+        if scope == "full" and self.full_po:
             return self.full_po.un_percentage()
-        if scope == 'part' and self.part_po:
+        if scope == "part" and self.part_po:
             return self.part_po.un_percentage()
         return 0
 
-    def un_word_percentage(self, scope='full'):
-        if scope == 'full' and self.full_po:
+    def un_word_percentage(self, scope="full"):
+        if scope == "full" and self.full_po:
             return self.full_po.un_word_percentage()
-        if scope == 'part' and self.part_po:
+        if scope == "part" and self.part_po:
             return self.part_po.un_word_percentage()
         return 0
 
     def get_lang(self):
         if not self.is_pot_stats():
             return _("%(lang_name)s (%(lang_locale)s)") % {
-                'lang_name': _(self.language.name),
-                'lang_locale': self.language.locale
+                "lang_name": _(self.language.name),
+                "lang_locale": self.language.locale,
             }
         return "pot file"
 
@@ -1552,7 +1573,10 @@ class Statistics(models.Model):
     def filename(self, potfile=False, reduced=False):
         if not self.is_pot_stats() and not potfile:
             return "%s.%s.%s.%spo" % (
-                self.domain.potbase(), self.branch.name_escaped, self.language.locale, reduced and 
"reduced." or ""
+                self.domain.potbase(),
+                self.branch.name_escaped,
+                self.language.locale,
+                reduced and "reduced." or "",
             )
         return "%s.%s.%spot" % (self.domain.potbase(), self.branch.name_escaped, reduced and "reduced." or 
"")
 
@@ -1563,20 +1587,25 @@ class Statistics(models.Model):
         pot_words_size = self.full_po.pot_size(words=True)
         fig_count = self.full_po.fig_count()
         # Return stat table header: 'POT file (n messages) - updated on ??-??-???? tz'
-        msg_text = ngettext("%(count)s message", "%(count)s messages", pot_size) % {'count': pot_size}
+        msg_text = ngettext("%(count)s message", "%(count)s messages", pot_size) % {"count": pot_size}
         upd_text = _("updated on %(date)s") % {
             # Date format syntax is similar to PHP http://www.php.net/date
-            'date': dateformat.format(self.full_po.updated, _("Y-m-d g:i a O"))
+            "date": dateformat.format(self.full_po.updated, _("Y-m-d g:i a O"))
         }
-        words_text = ngettext("%(count)s word", "%(count)s words", pot_words_size) % {'count': 
pot_words_size}
+        words_text = ngettext("%(count)s word", "%(count)s words", pot_words_size) % {"count": 
pot_words_size}
         if fig_count:
-            fig_text = ngettext("%(count)s figure", "%(count)s figures", fig_count) % {'count': fig_count}
+            fig_text = ngettext("%(count)s figure", "%(count)s figures", fig_count) % {"count": fig_count}
             text = _("POT file (%(messages)s — %(words)s, %(figures)s) — %(updated)s") % {
-                'messages': msg_text, 'figures': fig_text, 'updated': upd_text, 'words': words_text
+                "messages": msg_text,
+                "figures": fig_text,
+                "updated": upd_text,
+                "words": words_text,
             }
         else:
             text = _("POT file (%(messages)s — %(words)s) — %(updated)s") % {
-                'messages': msg_text, 'updated': upd_text, 'words': words_text
+                "messages": msg_text,
+                "updated": upd_text,
+                "words": words_text,
             }
         return text
 
@@ -1584,62 +1613,69 @@ class Statistics(models.Model):
         return bool(self.full_po and self.full_po.figures)
 
     def get_figures(self):
-        """ Return an enriched list of figure dicts (used in module_images.html):
-            [{'path':, 'hash':, 'fuzzy':, 'translated':, 'translated_file':}, ...] """
+        """Return an enriched list of figure dicts (used in module_images.html):
+        [{'path':, 'hash':, 'fuzzy':, 'translated':, 'translated_file':}, ...]"""
         figures = []
-        if self.full_po and self.domain.dtype == 'doc' and self.full_po.figures:
+        if self.full_po and self.domain.dtype == "doc" and self.full_po.figures:
             # something like: "https://gitlab.gnome.org/GNOME/vinagre / raw / master / help / %s / %s"
             url_model = utils.url_join(
-                self.branch.get_vcs_web_url(), self.branch.img_url_prefix,
-                self.branch.name, self.domain.base_dir, '%s', '%s'
+                self.branch.get_vcs_web_url(),
+                self.branch.img_url_prefix,
+                self.branch.name,
+                self.domain.base_dir,
+                "%s",
+                "%s",
             )
             for fig in self.full_po.figures:
                 fig2 = fig.copy()
-                fig2['orig_remote_url'] = url_model % ('C', fig['path'])
-                fig2['translated_file'] = False
+                fig2["orig_remote_url"] = url_model % ("C", fig["path"])
+                fig2["translated_file"] = False
                 # Check if a translated figure really exists or if the English one is used
-                if (self.language and
-                        (self.branch.co_path / self.domain.base_dir / self.language.locale
-                         / fig['path']).exists()):
-                    fig2['trans_remote_url'] = url_model % (self.language.locale, fig['path'])
-                    fig2['translated_file'] = True
+                if (
+                    self.language
+                    and (self.branch.co_path / self.domain.base_dir / self.language.locale / 
fig["path"]).exists()
+                ):
+                    fig2["trans_remote_url"] = url_model % (self.language.locale, fig["path"])
+                    fig2["translated_file"] = True
                 figures.append(fig2)
         return figures
 
     def fig_stats(self):
-        stats = {'fuzzy': 0, 'translated': 0, 'total': 0, 'prc': 0}
+        stats = {"fuzzy": 0, "translated": 0, "total": 0, "prc": 0}
         if self.full_po and self.full_po.figures:
             for fig in self.full_po.figures:
-                stats['total'] += 1
-                if fig.get('fuzzy', 0):
-                    stats['fuzzy'] += 1
+                stats["total"] += 1
+                if fig.get("fuzzy", 0):
+                    stats["fuzzy"] += 1
                 else:
-                    if fig.get('translated', 0):
-                        stats['translated'] += 1
-        stats['untranslated'] = stats['total'] - (stats['translated'] + stats['fuzzy'])
-        if stats['total'] > 0:
-            stats['prc'] = 100 * stats['translated'] / stats['total']
+                    if fig.get("translated", 0):
+                        stats["translated"] += 1
+        stats["untranslated"] = stats["total"] - (stats["translated"] + stats["fuzzy"])
+        if stats["total"] > 0:
+            stats["prc"] = 100 * stats["translated"] / stats["total"]
         return stats
 
     def vcs_web_path(self):
-        """ Return the Web interface path of file on remote vcs """
+        """Return the Web interface path of file on remote vcs"""
         return utils.url_join(self.branch.get_vcs_web_url(), self.domain.base_dir)
 
     def po_path(self, potfile=False, reduced=False):
-        """ Return path of (merged) po file on local filesystem """
+        """Return path of (merged) po file on local filesystem"""
         subdir = ""
         if self.domain.dtype == "doc":
             subdir = "docs"
         path = (
-            settings.POTDIR / f"{self.module_name}.{self.branch.name_escaped}" /
-            subdir / self.filename(potfile, reduced)
+            settings.POTDIR
+            / f"{self.module_name}.{self.branch.name_escaped}"
+            / subdir
+            / self.filename(potfile, reduced)
         )
         if reduced and not path.exists():
             path = self.po_path(potfile=potfile, reduced=False)
         return path
 
     def po_url(self, potfile=False, reduced=False):
-        """ Return URL of (merged) po file, e.g. for downloading the file """
+        """Return URL of (merged) po file, e.g. for downloading the file"""
         subdir = ""
         if self.domain.dtype == "doc":
             subdir = "docs/"
@@ -1659,20 +1695,23 @@ class Statistics(models.Model):
         if file_path is None and self.full_po:
             file_path = self.full_po.full_path
         stats = utils.po_file_stats(file_path)
-        for err in stats['errors']:
+        for err in stats["errors"]:
             self.set_error(*err)
         # If stats are 100%, compare the total number of strings between
         # committed po (pofile) and merged po (outpo).
         # Until https://savannah.gnu.org/bugs/index.php?50910 is fixed.
-        if self.language and stats['fuzzy'] + stats['untranslated'] == 0:  # Fully translated po file
+        if self.language and stats["fuzzy"] + stats["untranslated"] == 0:  # Fully translated po file
             abs_po_path = self.branch.co_path / self.domain.get_po_path(self.language.locale)
             if abs_po_path.exists():
                 git_stats = utils.po_file_stats(abs_po_path)
-                if stats['translated'] > git_stats['translated']:
-                    self.set_error('warn', gettext_noop(
-                        "The currently committed file has less translated strings. "
-                        "You should probably commit this file."
-                    ))
+                if stats["translated"] > git_stats["translated"]:
+                    self.set_error(
+                        "warn",
+                        gettext_noop(
+                            "The currently committed file has less translated strings. "
+                            "You should probably commit this file."
+                        ),
+                    )
         try:
             df = self.domain.doc_format(self.branch)
         except Exception:
@@ -1684,12 +1723,12 @@ class Statistics(models.Model):
             self.full_po = PoFile.objects.create(path=relpath)
             self.save()
         self.full_po.path = relpath
-        self.full_po.translated = int(stats['translated'])
-        self.full_po.fuzzy = int(stats['fuzzy'])
-        self.full_po.untranslated = int(stats['untranslated'])
-        self.full_po.translated_words = int(stats['translated_words'])
-        self.full_po.fuzzy_words = int(stats['fuzzy_words'])
-        self.full_po.untranslated_words = int(stats['untranslated_words'])
+        self.full_po.translated = int(stats["translated"])
+        self.full_po.fuzzy = int(stats["fuzzy"])
+        self.full_po.untranslated = int(stats["untranslated"])
+        self.full_po.translated_words = int(stats["translated_words"])
+        self.full_po.fuzzy_words = int(stats["fuzzy_words"])
+        self.full_po.untranslated_words = int(stats["untranslated_words"])
         self.full_po.figures = fig_stats
         self.full_po.updated = timezone.now()
         self.full_po.save()
@@ -1705,18 +1744,22 @@ class Statistics(models.Model):
                 self.save()
 
             # Try to compute a reduced po file
-            if ((self.full_po.fuzzy + self.full_po.untranslated) > 0
-                    and not self.branch.is_archive_only()
-                    and self.domain.red_filter != '-'):
+            if (
+                (self.full_po.fuzzy + self.full_po.untranslated) > 0
+                and not self.branch.is_archive_only()
+                and self.domain.red_filter != "-"
+            ):
                 # Generate partial_po and store partial stats
-                if self.full_po.path.endswith('.pot'):
+                if self.full_po.path.endswith(".pot"):
                     part_po_path = self.full_po.full_path.with_suffix(".reduced.pot")
                 else:
                     part_po_path = self.full_po.full_path.with_suffix(".reduced.po")
                 utils.po_grep(self.full_po.full_path, str(part_po_path), self.domain.red_filter)
                 part_stats = utils.po_file_stats(part_po_path)
-                if (part_stats['translated'] + part_stats['fuzzy'] + part_stats['untranslated'] ==
-                        stats['translated'] + stats['fuzzy'] + stats['untranslated']):
+                if (
+                    part_stats["translated"] + part_stats["fuzzy"] + part_stats["untranslated"]
+                    == stats["translated"] + stats["fuzzy"] + stats["untranslated"]
+                ):
                     # No possible gain, set part_po = full_po so it is possible to
                     # compute complete stats at database level
                     part_po_equals_full_po()
@@ -1728,12 +1771,12 @@ class Statistics(models.Model):
                         self.part_po = PoFile.objects.create(path=relpath)
                         self.save()
                     self.part_po.path = relpath
-                    self.part_po.translated = part_stats['translated']
-                    self.part_po.fuzzy = part_stats['fuzzy']
-                    self.part_po.untranslated = part_stats['untranslated']
-                    self.part_po.translated_words = part_stats['translated_words']
-                    self.part_po.fuzzy_words = part_stats['fuzzy_words']
-                    self.part_po.untranslated_words = part_stats['untranslated_words']
+                    self.part_po.translated = part_stats["translated"]
+                    self.part_po.fuzzy = part_stats["fuzzy"]
+                    self.part_po.untranslated = part_stats["untranslated"]
+                    self.part_po.translated_words = part_stats["translated_words"]
+                    self.part_po.fuzzy_words = part_stats["fuzzy_words"]
+                    self.part_po.untranslated_words = part_stats["untranslated_words"]
                     self.part_po.updated = timezone.now()
                     self.part_po.save()
             else:
@@ -1741,7 +1784,8 @@ class Statistics(models.Model):
 
         elif self.domain.dtype == "doc" and self.language is not None:
             fig_errors = utils.check_identical_figures(
-                fig_stats, self.branch.domain_path(self.domain), self.language.locale)
+                fig_stats, self.branch.domain_path(self.domain), self.language.locale
+            )
             for err in fig_errors:
                 self.set_error(*err)
 
@@ -1751,90 +1795,98 @@ class Statistics(models.Model):
         Information.objects.create(statistics=self, type=tp, description=description)
 
     def most_important_message(self):
-        """ Return a message of type 1.'error', or 2.'warn, or 3.'warn """
+        """Return a message of type 1.'error', or 2.'warn, or 3.'warn"""
         error = None
         for e in self.information_set.all():
-            if (not error
-                    or e.type in ('error', 'error-ext')
-                    or (e.type in ('warn', 'warn-ext') and error.type == 'info')):
+            if (
+                not error
+                or e.type in ("error", "error-ext")
+                or (e.type in ("warn", "warn-ext") and error.type == "info")
+            ):
                 error = e
         return error
 
     @classmethod
     def get_lang_stats_by_type(cls, lang, dtype, release):
-        """ Cook statistics for an entire release, a domain type dtype and the language lang.
-            Structure of the resulting stats dictionary is as follows:
-            stats = {
-                'dtype':dtype, # 'ui' or 'doc'
-                'total': 0,
-                'totaltrans': 0,
-                'totalfuzzy': 0,
-                'totaluntrans': 0,
-                'totaltransperc': 0,
-                'totalfuzzyperc': 0,
-                'totaluntransperc': 0,
-                'categs': {  # OrderedDict
-                    <categkey>: {
-                        'catname': <catname>, # translated category name
-                        'cattotal': 0,
-                        'cattrans': 0,
-                        'catfuzzy': 0,
-                        'catuntrans': 0,
-                        'cattransperc': 0,
-                        'modules': { # OrderedDict
-                            <module>: {
-                                <branchname>:
-                                    [(<domname>, <stat>), ...], # List of tuples (domain name, Statistics 
object)
-                                           # First element is a placeholder for a FakeSummaryStatistics 
object
-                                           # only used for summary if module has more than 1 domain
-                                }
+        """Cook statistics for an entire release, a domain type dtype and the language lang.
+        Structure of the resulting stats dictionary is as follows:
+        stats = {
+            'dtype':dtype, # 'ui' or 'doc'
+            'total': 0,
+            'totaltrans': 0,
+            'totalfuzzy': 0,
+            'totaluntrans': 0,
+            'totaltransperc': 0,
+            'totalfuzzyperc': 0,
+            'totaluntransperc': 0,
+            'categs': {  # OrderedDict
+                <categkey>: {
+                    'catname': <catname>, # translated category name
+                    'cattotal': 0,
+                    'cattrans': 0,
+                    'catfuzzy': 0,
+                    'catuntrans': 0,
+                    'cattransperc': 0,
+                    'modules': { # OrderedDict
+                        <module>: {
+                            <branchname>:
+                                [(<domname>, <stat>), ...], # List of tuples (domain name, Statistics object)
+                                       # First element is a placeholder for a FakeSummaryStatistics object
+                                       # only used for summary if module has more than 1 domain
                             }
                         }
                     }
-                },
-                'all_errors':[]
-            }
+                }
+            },
+            'all_errors':[]
+        }
         """
         # Import here to prevent a circular dependency
         # pylint: disable=import-outside-toplevel
-        from vertimus.models import State, Action
+        from vertimus.models import Action, State
 
         scope = "full"
-        if dtype.endswith('-part'):
+        if dtype.endswith("-part"):
             dtype = dtype[:-5]
             scope = "part"
 
         stats = {
-            'dtype': dtype, 'totaltrans': 0, 'totalfuzzy': 0, 'totaluntrans': 0,
-            'totaltransperc': 0, 'totalfuzzyperc': 0, 'totaluntransperc': 0,
-            'categs': OrderedDict(), 'all_errors': [],
+            "dtype": dtype,
+            "totaltrans": 0,
+            "totalfuzzy": 0,
+            "totaluntrans": 0,
+            "totaltransperc": 0,
+            "totalfuzzyperc": 0,
+            "totaluntransperc": 0,
+            "categs": OrderedDict(),
+            "all_errors": [],
         }
         # Sorted by module to allow grouping ('fake' stats)
-        pot_stats = Statistics.objects.select_related('domain', 'branch__module', 'full_po', 'part_po')
+        pot_stats = Statistics.objects.select_related("domain", "branch__module", "full_po", "part_po")
         if release:
-            pot_stats = pot_stats.filter(
-                language=None, branch__releases=release, domain__dtype=dtype
-            ).order_by('branch__module__name')
+            pot_stats = pot_stats.filter(language=None, branch__releases=release, 
domain__dtype=dtype).order_by(
+                "branch__module__name"
+            )
             categ_names = {
                 cat.branch_id: cat.name.name
-                for cat in Category.objects.select_related('branch', 'name').filter(release=release)
+                for cat in Category.objects.select_related("branch", "name").filter(release=release)
             }
         else:
-            pot_stats = pot_stats.filter(language=None, domain__dtype=dtype).order_by('branch__module__name')
+            pot_stats = pot_stats.filter(language=None, domain__dtype=dtype).order_by("branch__module__name")
 
-        tr_stats = Statistics.objects.select_related('domain', 'language', 'branch__module', 'full_po', 
'part_po')
+        tr_stats = Statistics.objects.select_related("domain", "language", "branch__module", "full_po", 
"part_po")
         if release:
-            tr_stats = tr_stats.filter(
-                language=lang, branch__releases=release, domain__dtype=dtype
-            ).order_by('branch__module__id')
+            tr_stats = tr_stats.filter(language=lang, branch__releases=release, 
domain__dtype=dtype).order_by(
+                "branch__module__id"
+            )
         else:
-            tr_stats = tr_stats.filter(language=lang, domain__dtype=dtype).order_by('branch__module__id')
+            tr_stats = tr_stats.filter(language=lang, domain__dtype=dtype).order_by("branch__module__id")
         tr_stats_dict = {f"{st.branch_id}-{st.domain_id}": st for st in tr_stats}
 
         infos_dict = Information.get_info_dict(lang)
 
         # Prepare State objects in a dict (with "branch_id-domain_id" as key), to save database queries later
-        vt_states = State.objects.select_related('branch', 'domain')
+        vt_states = State.objects.select_related("branch", "domain")
         if release:
             vt_states = vt_states.filter(language=lang, branch__releases=release, domain__dtype=dtype)
         else:
@@ -1842,7 +1894,7 @@ class Statistics(models.Model):
         vt_states_dict = {f"{vt.branch_id}-{vt.domain_id}": vt for vt in vt_states}
 
         # Get comments from last action of State objects
-        actions = Action.objects.filter(state_db__in=vt_states, comment__isnull=False).order_by('created')
+        actions = Action.objects.filter(state_db__in=vt_states, comment__isnull=False).order_by("created")
         actions_dict = {action.state_db_id: action for action in actions}
         for vt_state in vt_states_dict.values():
             if vt_state.id in actions_dict:
@@ -1857,11 +1909,14 @@ class Statistics(models.Model):
             domname = _(stat.domain.description) if stat.domain.description else ""
             branchname = stat.branch.name
             module = stat.branch.module
-            if categ_key not in stats['categs']:
-                stats['categs'][categ_key] = {
-                    'cattrans': 0, 'catfuzzy': 0, 'catuntrans': 0, 'cattransperc': 0,
-                    'catname': _(categ_key),
-                    'modules': OrderedDict(),
+            if categ_key not in stats["categs"]:
+                stats["categs"][categ_key] = {
+                    "cattrans": 0,
+                    "catfuzzy": 0,
+                    "catuntrans": 0,
+                    "cattransperc": 0,
+                    "catname": _(categ_key),
+                    "modules": OrderedDict(),
                 }
             # Try to get translated stat, else stick with POT stat
             br_dom_key = "%d-%d" % (stat.branch.id, stat.domain.id)
@@ -1872,61 +1927,62 @@ class Statistics(models.Model):
             # Match stat with error list
             if stat.id in infos_dict:
                 stat.info_list = infos_dict[stat.id]
-                stats['all_errors'].extend(stat.info_list)
+                stats["all_errors"].extend(stat.info_list)
 
             # Search if a state exists for this statistic
             if br_dom_key in vt_states_dict:
                 stat.state = vt_states_dict[br_dom_key]
-                if not stat.active and stat.state.name != 'None':
+                if not stat.active and stat.state.name != "None":
                     stat.active = True
 
-            stats['totaltrans'] += stat.translated(scope)
-            stats['totalfuzzy'] += stat.fuzzy(scope)
-            stats['totaluntrans'] += stat.untranslated(scope)
-            stats['categs'][categ_key]['cattrans'] += stat.translated(scope)
-            stats['categs'][categ_key]['catfuzzy'] += stat.fuzzy(scope)
-            stats['categs'][categ_key]['catuntrans'] += stat.untranslated(scope)
-            if module not in stats['categs'][categ_key]['modules']:
+            stats["totaltrans"] += stat.translated(scope)
+            stats["totalfuzzy"] += stat.fuzzy(scope)
+            stats["totaluntrans"] += stat.untranslated(scope)
+            stats["categs"][categ_key]["cattrans"] += stat.translated(scope)
+            stats["categs"][categ_key]["catfuzzy"] += stat.fuzzy(scope)
+            stats["categs"][categ_key]["catuntrans"] += stat.untranslated(scope)
+            if module not in stats["categs"][categ_key]["modules"]:
                 # first element is a placeholder for a fake stat
-                stats['categs'][categ_key]['modules'][module] = {branchname: [[' fake', None], [domname, 
stat]]}
-            elif branchname not in stats['categs'][categ_key]['modules'][module]:
+                stats["categs"][categ_key]["modules"][module] = {branchname: [[" fake", None], [domname, 
stat]]}
+            elif branchname not in stats["categs"][categ_key]["modules"][module]:
                 # first element is a placeholder for a fake stat
-                stats['categs'][categ_key]['modules'][module][branchname] = [[' fake', None], [domname, 
stat]]
+                stats["categs"][categ_key]["modules"][module][branchname] = [[" fake", None], [domname, 
stat]]
             else:
                 # Here we add the 2nd or more stat to the same module-branch
-                if len(stats['categs'][categ_key]['modules'][module][branchname]) == 2:
+                if len(stats["categs"][categ_key]["modules"][module][branchname]) == 2:
                     # Create a fake statistics object for module summary
-                    stats['categs'][categ_key]['modules'][module][branchname][0][1] = FakeSummaryStatistics(
+                    stats["categs"][categ_key]["modules"][module][branchname][0][1] = FakeSummaryStatistics(
                         stat.domain.module, stat.branch, dtype
                     )
-                    stats['categs'][categ_key]['modules'][module][branchname][0][1].trans(
-                        stats['categs'][categ_key]['modules'][module][branchname][1][1]
+                    stats["categs"][categ_key]["modules"][module][branchname][0][1].trans(
+                        stats["categs"][categ_key]["modules"][module][branchname][1][1]
                     )
-                stats['categs'][categ_key]['modules'][module][branchname].append([domname, stat])
-                stats['categs'][categ_key]['modules'][module][branchname][0][1].trans(stat)
+                stats["categs"][categ_key]["modules"][module][branchname].append([domname, stat])
+                stats["categs"][categ_key]["modules"][module][branchname][0][1].trans(stat)
 
         # Compute percentages and sorting
-        stats['total'] = stats['totaltrans'] + stats['totalfuzzy'] + stats['totaluntrans']
-        if stats['total'] > 0:
-            stats['totaltransperc'] = int(100 * stats['totaltrans'] / stats['total'])
-            stats['totalfuzzyperc'] = int(100 * stats['totalfuzzy'] / stats['total'])
-            stats['totaluntransperc'] = int(100 * stats['totaluntrans'] / stats['total'])
-        stats['categs'] = OrderedDict(sorted(stats['categs'].items(), key=lambda t: t[0]))
-        for categ in stats['categs'].values():
-            categ['cattotal'] = categ['cattrans'] + categ['catfuzzy'] + categ['catuntrans']
-            if categ['cattotal'] > 0:
-                categ['cattransperc'] = int(100 * categ['cattrans'] / categ['cattotal'])
+        stats["total"] = stats["totaltrans"] + stats["totalfuzzy"] + stats["totaluntrans"]
+        if stats["total"] > 0:
+            stats["totaltransperc"] = int(100 * stats["totaltrans"] / stats["total"])
+            stats["totalfuzzyperc"] = int(100 * stats["totalfuzzy"] / stats["total"])
+            stats["totaluntransperc"] = int(100 * stats["totaluntrans"] / stats["total"])
+        stats["categs"] = OrderedDict(sorted(stats["categs"].items(), key=lambda t: t[0]))
+        for categ in stats["categs"].values():
+            categ["cattotal"] = categ["cattrans"] + categ["catfuzzy"] + categ["catuntrans"]
+            if categ["cattotal"] > 0:
+                categ["cattransperc"] = int(100 * categ["cattrans"] / categ["cattotal"])
             # Sort domains
-            for mod in categ['modules'].values():
+            for mod in categ["modules"].values():
                 for doms in mod.values():
                     doms.sort(key=itemgetter(0))
         # Sort errors
-        stats['all_errors'].sort()
+        stats["all_errors"].sort()
         return stats
 
 
 class FakeLangStatistics:
-    """ Statistics class for a non existing lang stats """
+    """Statistics class for a non existing lang stats"""
+
     is_fake = True
 
     def __init__(self, pot_stat, lang):
@@ -1939,8 +1995,8 @@ class FakeLangStatistics:
 
     def get_lang(self):
         return _("%(lang_name)s (%(lang_locale)s)") % {
-            'lang_name': _(self.language.name),
-            'lang_locale': self.language.locale
+            "lang_name": _(self.language.name),
+            "lang_locale": self.language.locale,
         }
 
     def po_url(self, potfile=False, reduced=False):
@@ -1948,16 +2004,20 @@ class FakeLangStatistics:
             locale = "%s-reduced" % self.language.locale
         else:
             locale = self.language.locale
-        return reverse('dynamic_po', kwargs={
-            'module_name': self.branch.module.name,
-            'domain_name': self.domain.name,
-            'branch_name': self.branch.name,
-            'filename': "%s.po" % locale,
-        })
+        return reverse(
+            "dynamic_po",
+            kwargs={
+                "module_name": self.branch.module.name,
+                "domain_name": self.domain.name,
+                "branch_name": self.branch.name,
+                "filename": "%s.po" % locale,
+            },
+        )
 
 
 class FakeSummaryStatistics:
-    """ Statistics class that sums up an entire module stats """
+    """Statistics class that sums up an entire module stats"""
+
     is_fake = True
 
     def __init__(self, module, branch, dtype):
@@ -1999,17 +2059,17 @@ class FakeSummaryStatistics:
     def pot_size(self):
         return int(self._translated) + int(self._fuzzy) + int(self._untranslated)
 
-    def tr_percentage(self, scope='full'):
+    def tr_percentage(self, scope="full"):
         if self.pot_size() == 0:
             return 0
         return int(100 * self._translated / self.pot_size())
 
-    def fu_percentage(self, scope='full'):
+    def fu_percentage(self, scope="full"):
         if self.pot_size() == 0:
             return 0
         return int(100 * self._fuzzy / self.pot_size())
 
-    def un_percentage(self, scope='full'):
+    def un_percentage(self, scope="full"):
         if self.pot_size() == 0:
             return 0
         return int(100 * self._untranslated / self.pot_size())
@@ -2027,33 +2087,33 @@ class StatisticsArchived(models.Model):
     untranslated = models.IntegerField(default=0)
 
     class Meta:
-        db_table = 'statistics_archived'
+        db_table = "statistics_archived"
 
 
 INFORMATION_TYPE_CHOICES = (
-    ('info', 'Information'),
-    ('warn', 'Warning'),
-    ('error', 'Error'),
+    ("info", "Information"),
+    ("warn", "Warning"),
+    ("error", "Error"),
     # Type of warning/error external to po file itself (LINGUAS, images, etc.)
     # po files containing these are always rechecked
-    ('warn-ext', 'Warning (external)'),
-    ('error-ext', 'Error (external)')
+    ("warn-ext", "Warning (external)"),
+    ("error-ext", "Error (external)"),
 )
 
 
 class Information(models.Model):
-    statistics = models.ForeignKey('Statistics', on_delete=models.CASCADE)
+    statistics = models.ForeignKey("Statistics", on_delete=models.CASCADE)
     # Priority of a stats message
     type = models.CharField(max_length=10, choices=INFORMATION_TYPE_CHOICES)
     description = models.TextField()
 
     class Meta:
-        db_table = 'information'
+        db_table = "information"
 
     @classmethod
     def get_info_dict(cls, lang):
-        """ Return a dict (of lists) with all Information objects for a lang, with statistics_id as the key
-            Used for caching and preventing db access when requesting these objects for a long list of stats 
"""
+        """Return a dict (of lists) with all Information objects for a lang, with statistics_id as the key
+        Used for caching and preventing db access when requesting these objects for a long list of stats"""
         info_dict = {}
         for info in Information.objects.filter(statistics__language=lang):
             if info.statistics_id in info_dict:
@@ -2070,34 +2130,34 @@ class Information(models.Model):
 
     def get_description(self):
         text = self.description
-        matches = re.findall('###([^#]*)###', text)
+        matches = re.findall("###([^#]*)###", text)
         if matches:
-            text = re.sub('###([^#]*)###', '%s', text)
+            text = re.sub("###([^#]*)###", "%s", text)
 
         text = _(text)
 
         #  FIXME: if multiple substitutions, works only if order of %s is unchanged in translated string
         for match in matches:
-            text = text.replace('%s', match, 1)
+            text = text.replace("%s", match, 1)
         return text
 
     def report_bug_url(self):
         link = self.statistics.branch.module.get_bugs_enter_url()
         link += "&short_desc=%(short)s&content=%(short)s&comment=%(long)s" % {
-            'short': "Error regenerating POT file",
-            'long': utils.ellipsize(utils.stripHTML(self.get_description()), 1600),
+            "short": "Error regenerating POT file",
+            "long": utils.ellipsize(utils.stripHTML(self.get_description()), 1600),
         }
         return link
 
 
 class InformationArchived(models.Model):
-    statistics = models.ForeignKey('StatisticsArchived', on_delete=models.CASCADE)
+    statistics = models.ForeignKey("StatisticsArchived", on_delete=models.CASCADE)
     # Priority of a stats message
     type = models.CharField(max_length=10, choices=INFORMATION_TYPE_CHOICES)
     description = models.TextField()
 
     class Meta:
-        db_table = 'information_archived'
+        db_table = "information_archived"
 
 
 # Utilities to properly sort branch names like gnome-3-2 < gnome-3-10
diff --git a/stats/potdiff.py b/stats/potdiff.py
index 63875b92..386186cb 100644
--- a/stats/potdiff.py
+++ b/stats/potdiff.py
@@ -37,6 +37,7 @@ def diff(pota, potb):
         return result_all, result_add_only
     else:
         import difflib
+
         d = difflib.Differ()
         result = list(d.compare(res1, res2))
 
@@ -76,10 +77,10 @@ def _parse_contents(contents):
                 onemsg = ""
 
                 if msgctxt:
-                    onemsg += ('"' + msgctxt + '"::')
-                onemsg += ('"' + msgid + '"')
+                    onemsg += '"' + msgctxt + '"::'
+                onemsg += '"' + msgid + '"'
                 if plural:
-                    onemsg += ('/"' + plural + '"')
+                    onemsg += '/"' + plural + '"'
 
                 result.append(onemsg)
 
@@ -95,7 +96,7 @@ def _parse_contents(contents):
             plural = ""
             in_msgid_plural = 0
 
-        elif line[0] == "\"" and line[-1] == "\"":
+        elif line[0] == '"' and line[-1] == '"':
             if in_msgid:
                 if in_msgid_plural:
                     plural += line[1:-1]
diff --git a/stats/repos.py b/stats/repos.py
index 1434b303..31224a23 100644
--- a/stats/repos.py
+++ b/stats/repos.py
@@ -8,7 +8,7 @@ from stats.utils import STATUS_OK, url_join
 class RepoBase:
     @classmethod
     def repo_class_by_type(cls, tp):
-        return {'svn': SVNRepo, 'git': GitRepo, 'hg': MercurialRepo}.get(tp)
+        return {"svn": SVNRepo, "git": GitRepo, "hg": MercurialRepo}.get(tp)
 
     def __init__(self, branch):
         self.branch = branch
@@ -47,28 +47,28 @@ class GitRepo(RepoBase):
         if self.branch.is_head():
             # We are assuming here that this is the first branch created
             commands = [
-                ['git', 'clone', self.branch.module.vcs_root, str(self.branch.co_path)],
-                ['git', 'remote', 'update'],
-                ['git', 'checkout', self.branch.name],
+                ["git", "clone", self.branch.module.vcs_root, str(self.branch.co_path)],
+                ["git", "remote", "update"],
+                ["git", "checkout", self.branch.name],
             ]
         else:
             commands = [
-                ['git', 'pull'],
-                ['git', 'checkout', '--track', '-b', self.branch.name, 'origin/%s' % self.branch.name],
+                ["git", "pull"],
+                ["git", "checkout", "--track", "-b", self.branch.name, "origin/%s" % self.branch.name],
             ]
         # check if there are any submodules and init & update them
         commands.append("if [ -e .gitmodules ]; then git submodule update --init; fi")
         for cmd in commands:
-            working_dir = self.branch.co_path.parent if 'clone' in cmd else self.branch.co_path
+            working_dir = self.branch.co_path.parent if "clone" in cmd else self.branch.co_path
             run_shell_command(cmd, raise_on_error=True, cwd=working_dir)
 
     def update(self):
         # test "git checkout %(branch)s && git clean -dfq && git pull origin/%(branch)s"?
         commands = [
-            ['git', 'checkout', '-f', self.branch.name],
-            ['git', 'fetch'],
-            ['git', 'reset', '--hard', 'origin/%s' % self.branch.name],
-            ['git', 'clean', '-dfq'],
+            ["git", "checkout", "-f", self.branch.name],
+            ["git", "fetch"],
+            ["git", "reset", "--hard", "origin/%s" % self.branch.name],
+            ["git", "clean", "-dfq"],
             # check if there are any submodules and init & update them
             "if [ -e .gitmodules ]; then git submodule update --init; fi",
         ]
@@ -78,45 +78,41 @@ class GitRepo(RepoBase):
     def commit_files(self, files, message, author=None):
         base_path = self.branch.co_path
         for _file in files:
-            run_shell_command(['git', 'add', str(_file)], raise_on_error=True, cwd=base_path)
-        commit_cmd = ['git', 'commit', '-m', message]
+            run_shell_command(["git", "add", str(_file)], raise_on_error=True, cwd=base_path)
+        commit_cmd = ["git", "commit", "-m", message]
         if author:
-            commit_cmd.extend(['--author', author])
+            commit_cmd.extend(["--author", author])
         run_shell_command(commit_cmd, raise_on_error=True, cwd=base_path)
         # git push
         try:
-            run_shell_command(
-                ['git', 'push', 'origin', self.branch.name], raise_on_error=True, cwd=base_path
-            )
+            run_shell_command(["git", "push", "origin", self.branch.name], raise_on_error=True, 
cwd=base_path)
         except OSError:
             # Revert the commit
-            run_shell_command(['git', 'reset', '--hard', 'origin/%s' % self.branch.name], cwd=base_path)
+            run_shell_command(["git", "reset", "--hard", "origin/%s" % self.branch.name], cwd=base_path)
             raise
         else:
-            _, out, _ = run_shell_command(['git', 'log', '-n1', '--format=oneline'], cwd=base_path)
-            commit_hash = out.split()[0] if out else ''
+            _, out, _ = run_shell_command(["git", "log", "-n1", "--format=oneline"], cwd=base_path)
+            commit_hash = out.split()[0] if out else ""
         return commit_hash
 
     def cherry_pick(self, commit_hash):
         commit_dir = self.branch.co_path
-        result = run_shell_command(['git', 'cherry-pick', '-x', commit_hash], cwd=commit_dir)
+        result = run_shell_command(["git", "cherry-pick", "-x", commit_hash], cwd=commit_dir)
         if result[0] == STATUS_OK:
-            run_shell_command(
-                ['git', 'push', 'origin', self.branch.name], raise_on_error=True, cwd=commit_dir
-            )
+            run_shell_command(["git", "push", "origin", self.branch.name], raise_on_error=True, 
cwd=commit_dir)
             return True
         else:
             # Revert
-            run_shell_command(['git', 'cherry-pick', '--abort'], cwd=commit_dir)
+            run_shell_command(["git", "cherry-pick", "--abort"], cwd=commit_dir)
             return False
 
     def rename(self, old_name, new_name):
         commit_dir = self.branch.co_path
-        run_shell_command(['git', 'checkout', old_name], cwd=commit_dir)
-        run_shell_command(['git', 'branch', '-m', old_name, new_name], cwd=commit_dir)
-        run_shell_command(['git', 'fetch'], cwd=commit_dir)
-        run_shell_command(['git', 'branch', '--unset-upstream'], cwd=commit_dir)
-        run_shell_command(['git', 'branch', '--set-upstream-to', f'origin/{new_name}'], cwd=commit_dir)
+        run_shell_command(["git", "checkout", old_name], cwd=commit_dir)
+        run_shell_command(["git", "branch", "-m", old_name, new_name], cwd=commit_dir)
+        run_shell_command(["git", "fetch"], cwd=commit_dir)
+        run_shell_command(["git", "branch", "--unset-upstream"], cwd=commit_dir)
+        run_shell_command(["git", "branch", "--set-upstream-to", f"origin/{new_name}"], cwd=commit_dir)
 
     def remove(self):
         wdir = str(self.branch.co_path)
@@ -125,11 +121,8 @@ class GitRepo(RepoBase):
                 shutil.rmtree(wdir)
             else:
                 # get_branches()[0] because some modules have no main branch setup (release-notes)
-                run_shell_command(
-                    ['git', 'checkout', self.branch.module.get_branches()[0].name],
-                    cwd=wdir
-                )
-                run_shell_command(['git', 'branch', '-D', self.branch.name], cwd=wdir)
+                run_shell_command(["git", "checkout", self.branch.module.get_branches()[0].name], cwd=wdir)
+                run_shell_command(["git", "branch", "-D", self.branch.name], cwd=wdir)
 
 
 class SVNRepo(RepoBase):
@@ -140,12 +133,10 @@ class SVNRepo(RepoBase):
         else:
             co_url = url_join(root, self.branch.module.name, "branches", self.branch.name)
 
-        run_shell_command([
-            'svn', 'co', '--non-interactive', co_url, str(self.branch.co_path)
-        ], raise_on_error=True)
+        run_shell_command(["svn", "co", "--non-interactive", co_url, str(self.branch.co_path)], 
raise_on_error=True)
 
     def update(self):
-        run_shell_command(['svn', 'up', '--non-interactive'], raise_on_error=True, cwd=self.branch.co_path)
+        run_shell_command(["svn", "up", "--non-interactive"], raise_on_error=True, cwd=self.branch.co_path)
 
     def remove(self):
         if os.access(str(self.branch.co_path), os.W_OK):
@@ -158,11 +149,8 @@ class MercurialRepo(RepoBase):
 
     def init_checkout(self):
         base_path = self.branch.co_path
-        run_shell_command(
-            ['hg', 'clone', base_path, self.module.name],
-            cwd=base_path.parent
-        )
-        run_shell_command(['hg', 'update', self.branch.name], cwd=base_path)
+        run_shell_command(["hg", "clone", base_path, self.module.name], cwd=base_path.parent)
+        run_shell_command(["hg", "update", self.branch.name], cwd=base_path)
 
     def update(self):
-        run_shell_command(['hg', 'revert', '--all'], raise_on_error=True, cwd=self.branch.co_path)
+        run_shell_command(["hg", "revert", "--all"], raise_on_error=True, cwd=self.branch.co_path)
diff --git a/stats/templatetags/stats_extras.py b/stats/templatetags/stats_extras.py
index 9e07044a..16368423 100644
--- a/stats/templatetags/stats_extras.py
+++ b/stats/templatetags/stats_extras.py
@@ -4,21 +4,17 @@ from django.utils.html import format_html
 from django.utils.safestring import mark_safe
 from django.utils.translation import get_language_bidi
 
-from stats.models import (PoFile,
-                          Statistics,
-                          FakeLangStatistics,
-                          FakeSummaryStatistics, )
-
+from stats.models import FakeLangStatistics, FakeSummaryStatistics, PoFile, Statistics
 
 register = template.Library()
 
 STATISTICS_FULL = (
     '<pre class="stats">'
-    '<b>{prc:>3}%</b>'
+    "<b>{prc:>3}%</b>"
     '<span class="num1"> {translated:>6}</span>'
     '<span class="num2"> {fuzzy:>5}</span>'
     '<span class="num3"> {untranslated:>5}</span>'
-    '</pre>'
+    "</pre>"
 )
 
 STATISTICS_SHORT = (
@@ -36,15 +32,15 @@ PROGRESS_BAR = (
 
 @register.filter
 def linked_with(value, arg):
-    """ This filter returns an object (passed in value) enclosed with its
-        absolute url arg is the linked text """
+    """This filter returns an object (passed in value) enclosed with its
+    absolute url arg is the linked text"""
     return "<a href='%s'>%s</a>" % (value.get_absolute_url(), arg)
 
 
 @register.filter
 def support_class(value):
-    """ Returns a class depending on the coverage of the translation stats.
-        Value is a translation percentage """
+    """Returns a class depending on the coverage of the translation stats.
+    Value is a translation percentage"""
     if value >= 80:
         return "supported"
     elif value >= 50:
@@ -54,10 +50,10 @@ def support_class(value):
 
 @register.filter
 def support_class_total(stats):
-    """ Retuns a class depending on the trend. """
-    actual = stats['stats'][-1]
+    """Retuns a class depending on the trend."""
+    actual = stats["stats"][-1]
 
-    if stats['diff'] >= 0:
+    if stats["diff"] >= 0:
         return support_class(actual)
     else:
         if actual >= 80:
@@ -69,7 +65,7 @@ def support_class_total(stats):
 @register.filter
 def escapeat(value):
     """Replace '@' with '__', accepted sequence in JS ids."""
-    return value.replace('@', '__')
+    return value.replace("@", "__")
 
 
 @register.filter
@@ -78,80 +74,76 @@ def browse_bugs(module, content):
 
 
 @register.filter
-def num_stats(stat, scope='full'):
+def num_stats(stat, scope="full"):
     return num_stats_helper(stat, scope=scope, strings=True)
 
 
 @register.filter
-def num_word_stats(stat, scope='full'):
+def num_word_stats(stat, scope="full"):
     return num_stats_helper(stat, scope=scope, strings=False)
 
 
-def num_stats_helper(stat, scope='full', strings=True):
+def num_stats_helper(stat, scope="full", strings=True):
     """
     Produce stat numbers as in: 85% (1265/162/85)
     0s are hidden unless scope ends with ',zeros'
     """
-    if ',' in scope:
-        scope, zeros = scope.split(',', 1)
-        show_zeros = zeros == 'zeros'
+    if "," in scope:
+        scope, zeros = scope.split(",", 1)
+        show_zeros = zeros == "zeros"
     else:
         show_zeros = False
-    if isinstance(stat, (Statistics,
-                         FakeLangStatistics,
-                         FakeSummaryStatistics)):
+    if isinstance(stat, (Statistics, FakeLangStatistics, FakeSummaryStatistics)):
         if strings:
             stats = {
-                'prc': stat.tr_percentage(scope),
-                'translated': stat.translated(scope),
-                'fuzzy': stat.fuzzy(scope),
-                'untranslated': stat.untranslated(scope),
+                "prc": stat.tr_percentage(scope),
+                "translated": stat.translated(scope),
+                "fuzzy": stat.fuzzy(scope),
+                "untranslated": stat.untranslated(scope),
             }
         else:
             stats = {
-                'prc': stat.tr_word_percentage(scope),
-                'translated': stat.translated_words(scope),
-                'fuzzy': stat.fuzzy_words(scope),
-                'untranslated': stat.untranslated_words(scope),
+                "prc": stat.tr_word_percentage(scope),
+                "translated": stat.translated_words(scope),
+                "fuzzy": stat.fuzzy_words(scope),
+                "untranslated": stat.untranslated_words(scope),
             }
     elif isinstance(stat, PoFile):
         if strings:
             stats = {
-                'translated': stat.translated,
-                'fuzzy': stat.fuzzy,
-                'untranslated': stat.untranslated,
+                "translated": stat.translated,
+                "fuzzy": stat.fuzzy,
+                "untranslated": stat.untranslated,
             }
         else:
             stats = {
-                'translated': stat.translated_words,
-                'fuzzy': stat.fuzzy_words,
-                'untranslated': stat.untranslated_words,
+                "translated": stat.translated_words,
+                "fuzzy": stat.fuzzy_words,
+                "untranslated": stat.untranslated_words,
             }
-        if scope != 'short':
+        if scope != "short":
             if strings:
-                stats['prc'] = stat.tr_percentage()
+                stats["prc"] = stat.tr_percentage()
             else:
-                stats['prc'] = stat.tr_word_percentage()
+                stats["prc"] = stat.tr_word_percentage()
     else:
         stats = stat
-    if 'translated_perc' in stats:
-        stats['prc'] = stats['translated_perc']
-    if 'prc' in stats:
+    if "translated_perc" in stats:
+        stats["prc"] = stats["translated_perc"]
+    if "prc" in stats:
         template = STATISTICS_FULL
         if not show_zeros:
-            stats = {k: (' ' if v == 0 and k != 'prc' else v) for k, v in stats.items()}
+            stats = {k: (" " if v == 0 and k != "prc" else v) for k, v in stats.items()}
     else:
         template = STATISTICS_SHORT
     return format_html(template, **stats)
 
 
 @register.filter
-def vis_stats(stat, scope='full'):
-    """ Produce visual stats with green/red bar """
+def vis_stats(stat, scope="full"):
+    """Produce visual stats with green/red bar"""
     bidi = get_language_bidi() and "right" or "left"
-    if isinstance(stat, (Statistics,
-                         FakeLangStatistics,
-                         FakeSummaryStatistics)):
+    if isinstance(stat, (Statistics, FakeLangStatistics, FakeSummaryStatistics)):
         trans = stat.tr_percentage(scope)
         fuzzy = stat.fu_percentage(scope)
         untrans = stat.un_percentage(scope)
@@ -160,28 +152,29 @@ def vis_stats(stat, scope='full'):
         fuzzy = stat.fu_percentage()
         untrans = stat.un_percentage()
     elif isinstance(stat, dict):
-        trans = stat['translated_perc']
-        fuzzy = stat['fuzzy_perc']
-        untrans = stat['untranslated_perc']
+        trans = stat["translated_perc"]
+        fuzzy = stat["fuzzy_perc"]
+        untrans = stat["untranslated_perc"]
     else:
         text = '<div class="untranslated" style="{dir}: 0px; width: 100px;"></div>'
         return format_html(text, dir=bidi)
 
-    return format_html(PROGRESS_BAR, **{
-        'dir': bidi,
-        'trans': trans,
-        'fuzzy': fuzzy,
-        'tr_fu': trans + fuzzy,
-        'untrans': untrans,
-    })
+    return format_html(
+        PROGRESS_BAR,
+        **{
+            "dir": bidi,
+            "trans": trans,
+            "fuzzy": fuzzy,
+            "tr_fu": trans + fuzzy,
+            "untrans": untrans,
+        }
+    )
 
 
 @register.filter
-def vis_word_stats(stat, scope='full'):
-    """ Produce visual stats with green/red bar """
-    if isinstance(stat, (Statistics,
-                         FakeLangStatistics,
-                         FakeSummaryStatistics)):
+def vis_word_stats(stat, scope="full"):
+    """Produce visual stats with green/red bar"""
+    if isinstance(stat, (Statistics, FakeLangStatistics, FakeSummaryStatistics)):
         trans = stat.tr_word_percentage(scope)
         fuzzy = stat.fu_word_percentage(scope)
         untrans = stat.un_word_percentage(scope)
@@ -190,26 +183,29 @@ def vis_word_stats(stat, scope='full'):
         fuzzy = stat.fu_word_percentage()
         untrans = stat.un_word_percentage()
     else:
-        trans = stat['translated_perc']
-        fuzzy = stat['fuzzy_perc']
-        untrans = stat['untranslated_perc']
-
-    return format_html(PROGRESS_BAR, **{
-        'dir': get_language_bidi() and "right" or "left",
-        'trans': trans,
-        'fuzzy': fuzzy,
-        'tr_fu': trans + fuzzy,
-        'untrans': untrans,
-    })
+        trans = stat["translated_perc"]
+        fuzzy = stat["fuzzy_perc"]
+        untrans = stat["untranslated_perc"]
+
+    return format_html(
+        PROGRESS_BAR,
+        **{
+            "dir": get_language_bidi() and "right" or "left",
+            "trans": trans,
+            "fuzzy": fuzzy,
+            "tr_fu": trans + fuzzy,
+            "untrans": untrans,
+        }
+    )
 
 
 @register.filter
 def is_video(fig):
-    return fig['path'].endswith('.ogv')
+    return fig["path"].endswith(".ogv")
 
 
 @register.filter(is_safe=True)
-def markdown(value, arg=''):
+def markdown(value, arg=""):
     """
     Copy of deprecated django.contrib.markup
     Runs Markdown over a given value, optionally using various
diff --git a/stats/tests/fixture_factory.py b/stats/tests/fixture_factory.py
index 0827de38..205eca62 100644
--- a/stats/tests/fixture_factory.py
+++ b/stats/tests/fixture_factory.py
@@ -4,12 +4,19 @@ from django.core.management import call_command
 from django.test import TestCase
 
 from languages.models import Language
-from teams.models import Team, Role
 from people.models import Person
 from stats.models import (
-    Branch, Category, CategoryName, Domain, Information, Module, PoFile,
-    Release, Statistics,
+    Branch,
+    Category,
+    CategoryName,
+    Domain,
+    Information,
+    Module,
+    PoFile,
+    Release,
+    Statistics,
 )
+from teams.models import Role, Team
 
 
 class FixtureFactory(TestCase):
@@ -22,48 +29,48 @@ class FixtureFactory(TestCase):
     def make_fixture(self):
         # Creating models: Teams
         t1 = Team.objects.create(
-            name='fr',
+            name="fr",
             description="French",
             webpage_url="http://gnomefr.traduc.org/";,
             mailing_list="gnomefr traduc org",
             mailing_list_subscribe="http://www.traduc.org/mailman/listinfo/gnomefr";,
             presentation="Here can come any custom description for a team",
         )
-        t2 = Team.objects.create(name='it', description="Italian", webpage_url="http://www.it.gnome.org/";)
+        t2 = Team.objects.create(name="it", description="Italian", webpage_url="http://www.it.gnome.org/";)
 
         # Creating models: Languages
-        Language.objects.create(name='en', locale='en', plurals="nplurals=2; plural=(n != 1)")
-        l_fr = Language.objects.create(name='French', locale='fr', plurals="nplurals=2; plural=(n > 1)", 
team=t1)
-        l_it = Language.objects.create(name='Italian', locale='it', plurals="nplurals=2; plural=(n != 1)", 
team=t2)
+        Language.objects.create(name="en", locale="en", plurals="nplurals=2; plural=(n != 1)")
+        l_fr = Language.objects.create(name="French", locale="fr", plurals="nplurals=2; plural=(n > 1)", 
team=t1)
+        l_it = Language.objects.create(name="Italian", locale="it", plurals="nplurals=2; plural=(n != 1)", 
team=t2)
         # Lang with no team and no stats
-        Language.objects.create(name='Bemba', locale='bem')
+        Language.objects.create(name="Bemba", locale="bem")
 
         # Creating models: Persons/Roles
-        p0 = Person.objects.create(username='admin1')  # Fake person (deleted below), just not to use pk=1 
for user
+        p0 = Person.objects.create(username="admin1")  # Fake person (deleted below), just not to use pk=1 
for user
         p1 = Person.objects.create(
-            first_name='Robert',
-            last_name='Translator',
-            email='bob example org',
-            username='bob',
-            irc_nick='bobby',
-            svn_account='bob1',
-        )
-        p1.set_password('bob')
-        Role.objects.create(team=t1, person=p1, role='translator')
+            first_name="Robert",
+            last_name="Translator",
+            email="bob example org",
+            username="bob",
+            irc_nick="bobby",
+            svn_account="bob1",
+        )
+        p1.set_password("bob")
+        Role.objects.create(team=t1, person=p1, role="translator")
         p2 = Person.objects.create(
-            first_name='John',
-            last_name='Coordinator',
-            email='coord example org',
-            username='coord',
-            svn_account='coord_fr',
-        )
-        p2.set_password('coord')
-        Role.objects.create(team=t1, person=p2, role='coordinator')
+            first_name="John",
+            last_name="Coordinator",
+            email="coord example org",
+            username="coord",
+            svn_account="coord_fr",
+        )
+        p2.set_password("coord")
+        Role.objects.create(team=t1, person=p2, role="coordinator")
         p3 = Person.objects.create(
-            first_name='Alessio', last_name='Reviewer', email='alessio example org', username='alessio'
+            first_name="Alessio", last_name="Reviewer", email="alessio example org", username="alessio"
         )
-        p1.set_password('alessio')
-        Role.objects.create(team=t2, person=p3, role='reviewer')
+        p1.set_password("alessio")
+        Role.objects.create(team=t2, person=p3, role="reviewer")
         p0.delete()
 
         # Creating models: Modules
@@ -89,42 +96,42 @@ class FixtureFactory(TestCase):
             vcs_web="https://gitlab.freedesktop.org/xdg/shared-mime-info";,
             bugs_base="https://gitlab.freedesktop.org/xdg/shared-mime-info/issues";,
             comment="This is not a GNOME-specific module. Please submit your translation "
-            "through the <a href=\"https://www.transifex.com/freedesktop/shared-mime-info/\";>Transifex 
platform</a>.",
+            'through the <a href="https://www.transifex.com/freedesktop/shared-mime-info/";>Transifex 
platform</a>.',
         )
 
         # Creating models: Domains
         dom = {}
         for mod in (gnome_hello, zenity, s_m_i):
-            dom['%s-ui' % mod.name] = Domain.objects.create(
-                module=mod, name='po', description='UI Translations', dtype='ui', layout='po/{lang}.po'
+            dom["%s-ui" % mod.name] = Domain.objects.create(
+                module=mod, name="po", description="UI Translations", dtype="ui", layout="po/{lang}.po"
             )
-            dom['%s-doc' % mod.name] = Domain.objects.create(
-                module=mod, name='help', description='User Guide', dtype='doc', 
layout='help/{lang}/{lang}.po'
+            dom["%s-doc" % mod.name] = Domain.objects.create(
+                module=mod, name="help", description="User Guide", dtype="doc", 
layout="help/{lang}/{lang}.po"
             )
 
         # Creating models: Branches
         Branch.checkout_on_creation = False
-        b1 = Branch(name='master', module=gnome_hello)
+        b1 = Branch(name="master", module=gnome_hello)
         b1.save(update_statistics=False)
-        b2 = Branch(name='gnome-3-8', module=zenity)
+        b2 = Branch(name="gnome-3-8", module=zenity)
         b2.save(update_statistics=False)
-        b3 = Branch(name='master', module=zenity)
+        b3 = Branch(name="master", module=zenity)
         b3.save(update_statistics=False)
-        b4 = Branch(name='master', module=s_m_i)
+        b4 = Branch(name="master", module=s_m_i)
         b4.save(update_statistics=False)
 
         # Creating models: Releases/Categories
         rel1 = Release.objects.create(
-            name='gnome-3-8', status='official', description='GNOME 3.8 (stable)', string_frozen=True
+            name="gnome-3-8", status="official", description="GNOME 3.8 (stable)", string_frozen=True
         )
         rel2 = Release.objects.create(
-            name='gnome-dev', status='official', description='GNOME in Development', string_frozen=False
+            name="gnome-dev", status="official", description="GNOME in Development", string_frozen=False
         )
         rel3 = Release.objects.create(
-            name='freedesktop-org', status='xternal', description='freedesktop.org (non-GNOME)', 
string_frozen=False
+            name="freedesktop-org", status="xternal", description="freedesktop.org (non-GNOME)", 
string_frozen=False
         )
 
-        cat_name = CategoryName.objects.create(name='Desktop')
+        cat_name = CategoryName.objects.create(name="Desktop")
         Category.objects.create(release=rel1, branch=b1, name=cat_name)
         Category.objects.create(release=rel2, branch=b1, name=cat_name)
         Category.objects.create(release=rel1, branch=b2, name=cat_name)
@@ -134,15 +141,15 @@ class FixtureFactory(TestCase):
         # gnome-hello ui, gnome-hello doc (POT, fr, it)
         pofile = PoFile.objects.create(untranslated=47)
         Statistics.objects.create(
-            branch=b1, domain=dom['gnome-hello-ui'], language=None, full_po=pofile, part_po=pofile
+            branch=b1, domain=dom["gnome-hello-ui"], language=None, full_po=pofile, part_po=pofile
         )
         pofile = PoFile.objects.create(translated=47)
         Statistics.objects.create(
-            branch=b1, domain=dom['gnome-hello-ui'], language=l_fr, full_po=pofile, part_po=pofile
+            branch=b1, domain=dom["gnome-hello-ui"], language=l_fr, full_po=pofile, part_po=pofile
         )
         pofile = PoFile.objects.create(translated=30, fuzzy=10, untranslated=7)
         Statistics.objects.create(
-            branch=b1, domain=dom['gnome-hello-ui'], language=l_it, full_po=pofile, part_po=pofile
+            branch=b1, domain=dom["gnome-hello-ui"], language=l_it, full_po=pofile, part_po=pofile
         )
         pofile = PoFile.objects.create(
             untranslated=20,
@@ -152,7 +159,7 @@ class FixtureFactory(TestCase):
             ],
         )
         Statistics.objects.create(
-            branch=b1, domain=dom['gnome-hello-doc'], language=None, full_po=pofile, part_po=pofile
+            branch=b1, domain=dom["gnome-hello-doc"], language=None, full_po=pofile, part_po=pofile
         )
         pofile = PoFile.objects.create(
             translated=20,
@@ -172,63 +179,63 @@ class FixtureFactory(TestCase):
             ],
         )
         Statistics.objects.create(
-            branch=b1, domain=dom['gnome-hello-doc'], language=l_fr, full_po=pofile, part_po=pofile
+            branch=b1, domain=dom["gnome-hello-doc"], language=l_fr, full_po=pofile, part_po=pofile
         )
         pofile = PoFile.objects.create(translated=20)
         Statistics.objects.create(
-            branch=b1, domain=dom['gnome-hello-doc'], language=l_it, full_po=pofile, part_po=pofile
+            branch=b1, domain=dom["gnome-hello-doc"], language=l_it, full_po=pofile, part_po=pofile
         )
         # zenity ui 3.8, zenity doc 3.8, zenity ui master, zenity doc master (POT, fr, it)
         pofile = PoFile.objects.create(untranslated=136)
         part_pofile = PoFile.objects.create(untranslated=128)
         Statistics.objects.create(
-            branch=b2, domain=dom['zenity-ui'], language=None, full_po=pofile, part_po=part_pofile
+            branch=b2, domain=dom["zenity-ui"], language=None, full_po=pofile, part_po=part_pofile
         )
         pofile = PoFile.objects.create(translated=136)
-        Statistics.objects.create(branch=b2, domain=dom['zenity-ui'], language=l_fr, full_po=pofile, 
part_po=pofile)
+        Statistics.objects.create(branch=b2, domain=dom["zenity-ui"], language=l_fr, full_po=pofile, 
part_po=pofile)
         pofile = PoFile.objects.create(translated=130, untranslated=6)
         part_pofile = PoFile.objects.create(translated=100, untranslated=28)
         Statistics.objects.create(
-            branch=b2, domain=dom['zenity-ui'], language=l_it, full_po=pofile, part_po=part_pofile
+            branch=b2, domain=dom["zenity-ui"], language=l_it, full_po=pofile, part_po=part_pofile
         )
         pofile = PoFile.objects.create(untranslated=259)
-        Statistics.objects.create(branch=b2, domain=dom['zenity-doc'], language=None, full_po=pofile, 
part_po=pofile)
+        Statistics.objects.create(branch=b2, domain=dom["zenity-doc"], language=None, full_po=pofile, 
part_po=pofile)
         pofile = PoFile.objects.create(untranslated=259)
-        Statistics.objects.create(branch=b2, domain=dom['zenity-doc'], language=l_fr, full_po=pofile, 
part_po=pofile)
+        Statistics.objects.create(branch=b2, domain=dom["zenity-doc"], language=l_fr, full_po=pofile, 
part_po=pofile)
         pofile = PoFile.objects.create(translated=259)
-        Statistics.objects.create(branch=b2, domain=dom['zenity-doc'], language=l_it, full_po=pofile, 
part_po=pofile)
+        Statistics.objects.create(branch=b2, domain=dom["zenity-doc"], language=l_it, full_po=pofile, 
part_po=pofile)
         pofile = PoFile.objects.create(untranslated=149)
         stat1 = Statistics.objects.create(
-            branch=b3, domain=dom['zenity-ui'], language=None, full_po=pofile, part_po=pofile
+            branch=b3, domain=dom["zenity-ui"], language=None, full_po=pofile, part_po=pofile
         )
         pofile = PoFile.objects.create(translated=255, fuzzy=4)
-        Statistics.objects.create(branch=b3, domain=dom['zenity-ui'], language=l_fr, full_po=pofile, 
part_po=pofile)
+        Statistics.objects.create(branch=b3, domain=dom["zenity-ui"], language=l_fr, full_po=pofile, 
part_po=pofile)
         pofile = PoFile.objects.create(translated=259)
-        Statistics.objects.create(branch=b3, domain=dom['zenity-ui'], language=l_it, full_po=pofile, 
part_po=pofile)
+        Statistics.objects.create(branch=b3, domain=dom["zenity-ui"], language=l_it, full_po=pofile, 
part_po=pofile)
         pofile = PoFile.objects.create(untranslated=259)
-        Statistics.objects.create(branch=b3, domain=dom['zenity-doc'], language=None, full_po=pofile, 
part_po=pofile)
+        Statistics.objects.create(branch=b3, domain=dom["zenity-doc"], language=None, full_po=pofile, 
part_po=pofile)
         pofile = PoFile.objects.create(untranslated=259)
-        Statistics.objects.create(branch=b3, domain=dom['zenity-doc'], language=l_fr, full_po=pofile, 
part_po=pofile)
+        Statistics.objects.create(branch=b3, domain=dom["zenity-doc"], language=l_fr, full_po=pofile, 
part_po=pofile)
         pofile = PoFile.objects.create(translated=259)
-        Statistics.objects.create(branch=b3, domain=dom['zenity-doc'], language=l_it, full_po=pofile, 
part_po=pofile)
+        Statistics.objects.create(branch=b3, domain=dom["zenity-doc"], language=l_it, full_po=pofile, 
part_po=pofile)
         # shared-mime-info ui (POT, fr, it)
         pofile = PoFile.objects.create(untranslated=626)
         Statistics.objects.create(
-            branch=b4, domain=dom['shared-mime-info-ui'], language=None, full_po=pofile, part_po=pofile
+            branch=b4, domain=dom["shared-mime-info-ui"], language=None, full_po=pofile, part_po=pofile
         )
         pofile = PoFile.objects.create(translated=598, fuzzy=20, untranslated=2)
         Statistics.objects.create(
-            branch=b4, domain=dom['shared-mime-info-ui'], language=l_fr, full_po=pofile, part_po=pofile
+            branch=b4, domain=dom["shared-mime-info-ui"], language=l_fr, full_po=pofile, part_po=pofile
         )
         pofile = PoFile.objects.create(translated=620, fuzzy=6)
         Statistics.objects.create(
-            branch=b4, domain=dom['shared-mime-info-ui'], language=l_it, full_po=pofile, part_po=pofile
+            branch=b4, domain=dom["shared-mime-info-ui"], language=l_it, full_po=pofile, part_po=pofile
         )
 
         # Example of error
         stat1.information_set.add(
             Information(
-                type='error',
+                type="error",
                 description=(
                     "Error regenerating POT file for zenity:\n<pre>intltool-update -g 'zenity' -p\nERROR:"
                     " xgettext failed to generate PO template file.</pre>"
@@ -239,10 +246,10 @@ class FixtureFactory(TestCase):
         # Output fixture
         out_file = NamedTemporaryFile(suffix=".json", dir=".", delete=False)
         call_command(
-            'dumpdata',
-            *['auth.User', 'people', 'teams', 'languages', 'stats'],
+            "dumpdata",
+            *["auth.User", "people", "teams", "languages", "stats"],
             indent=1,
-            format='json',
+            format="json",
             stdout=out_file
         )
         out_file.close()
diff --git a/stats/tests/tests.py b/stats/tests/tests.py
index d9670caa..6ee4b4f5 100644
--- a/stats/tests/tests.py
+++ b/stats/tests/tests.py
@@ -21,9 +21,20 @@ from languages.models import Language
 from people.models import Person
 from stats import utils
 from stats.models import (
-    GLIB_PRESET, Branch, CategoryName, Domain, FakeLangStatistics, Information,
-    Module, ModuleLock, PoFile, Release, Statistics, UnableToCommit
+    GLIB_PRESET,
+    Branch,
+    CategoryName,
+    Domain,
+    FakeLangStatistics,
+    Information,
+    Module,
+    ModuleLock,
+    PoFile,
+    Release,
+    Statistics,
+    UnableToCommit,
 )
+
 from .utils import PatchShellCommand, test_scratchdir
 
 try:
@@ -35,10 +46,10 @@ except ImportError:
 
 
 class ModuleTestCase(TestCase):
-    fixtures = ['sample_data.json']
+    fixtures = ["sample_data.json"]
     SYS_DEPENDENCIES = (
-        ('gettext', 'msgfmt'),
-        ('intltool', 'intltool-update'),
+        ("gettext", "msgfmt"),
+        ("intltool", "intltool-update"),
     )
 
     @classmethod
@@ -54,15 +65,15 @@ class ModuleTestCase(TestCase):
         self.branch = self.mod.branch_set.get(name="master")
 
     def test_module_methods(self):
-        self.assertEqual(self.mod.get_description(), 'gnome-hello')
+        self.assertEqual(self.mod.get_description(), "gnome-hello")
 
     def test_module_comparable(self):
-        modules = [Module.objects.get(name='zenity'), Module.objects.get(name='gnome-hello')]
-        self.assertEqual(sorted(modules)[0].name, 'gnome-hello')
+        modules = [Module.objects.get(name="zenity"), Module.objects.get(name="gnome-hello")]
+        self.assertEqual(sorted(modules)[0].name, "gnome-hello")
 
     def test_clean_module_locks(self):
         class CustomModuleLock(ModuleLock):
-            dir_prefix = 'updating-test-'
+            dir_prefix = "updating-test-"
 
         module = Module.objects.get(name="gnome-hello")
         with CustomModuleLock(module) as lock:
@@ -79,87 +90,77 @@ class ModuleTestCase(TestCase):
             mocked_sleep.assert_called_once()
 
     def test_modules_list(self):
-        response = self.client.get(reverse('modules'))
+        response = self.client.get(reverse("modules"))
         self.assertContains(response, '<li><a href="/module/zenity/">zenity</a></li>')
         # Also test JSON output
-        response = self.client.get(reverse('modules', args=['json']))
-        content = json.loads(response.content.decode('utf-8'))
+        response = self.client.get(reverse("modules", args=["json"]))
+        content = json.loads(response.content.decode("utf-8"))
         self.assertEqual(len(content), Module.objects.count())
-        self.assertEqual(
-            content[-1]["fields"]["vcs_root"],
-            'https://gitlab.gnome.org/GNOME/zenity.git'
-        )
+        self.assertEqual(content[-1]["fields"]["vcs_root"], "https://gitlab.gnome.org/GNOME/zenity.git";)
 
     def test_module_branch(self):
-        response = self.client.get(reverse('module_branch', args=['gnome-hello', 'master']))
+        response = self.client.get(reverse("module_branch", args=["gnome-hello", "master"]))
         self.assertContains(response, '<table class="stats table">')
         # Missing branch
-        with self.assertLogs('django.request', level='WARNING'):
-            response = self.client.get(reverse('module_branch', args=['gnome-hello', 'missing']))
+        with self.assertLogs("django.request", level="WARNING"):
+            response = self.client.get(reverse("module_branch", args=["gnome-hello", "missing"]))
         self.assertEqual(response.status_code, 404)
         # Branch name with slash is reverse-able
-        with self.assertLogs('django.request', level='WARNING'):
-            response = self.client.get(reverse('module_branch', args=['gnome-hello', 'with/slash']))
+        with self.assertLogs("django.request", level="WARNING"):
+            response = self.client.get(reverse("module_branch", args=["gnome-hello", "with/slash"]))
         self.assertEqual(response.status_code, 404)
 
     def test_module_bugs_reporting(self):
         self.assertEqual(
             self.mod.get_bugs_i18n_url(),
-            'https://gitlab.gnome.org/GNOME/gnome-hello/issues/?state=opened&label_name[]=8.%20Translation'
+            "https://gitlab.gnome.org/GNOME/gnome-hello/issues/?state=opened&label_name[]=8.%20Translation";,
         )
         self.assertEqual(
-            self.mod.get_bugs_i18n_url('pot error'),
-            'https://gitlab.gnome.org/GNOME/gnome-hello/issues/?state=opened&label_name[]'
-            '=8.%20Translation&search=pot+error'
+            self.mod.get_bugs_i18n_url("pot error"),
+            "https://gitlab.gnome.org/GNOME/gnome-hello/issues/?state=opened&label_name[]";
+            "=8.%20Translation&search=pot+error",
         )
-        response = self.client.get(reverse('module', args=[self.mod.name]))
+        response = self.client.get(reverse("module", args=[self.mod.name]))
         self.assertContains(
             response,
             '<li><a href="https://gitlab.gnome.org/GNOME/gnome-hello/issues/new";>Report a bug</a></li>',
-            html=True
+            html=True,
         )
-        self.mod.bugs_base = ''
+        self.mod.bugs_base = ""
         self.mod.save()
-        response = self.client.get(reverse('module', args=[self.mod.name]))
+        response = self.client.get(reverse("module", args=[self.mod.name]))
         self.assertContains(response, "Sorry, no known locations to report bugs for this module.")
 
     @test_scratchdir
     def test_first_branch_creation(self):
-        mod = Module.objects.create(name='eog', vcs_type='git', 
vcs_root='https://gitlab.gnome.org/GNOME/eog.git')
-        br = Branch(module=mod, name='master')
+        mod = Module.objects.create(name="eog", vcs_type="git", 
vcs_root="https://gitlab.gnome.org/GNOME/eog.git";)
+        br = Branch(module=mod, name="master")
         with PatchShellCommand() as cmds:
             br.checkout()
         self.assertTrue(
-            'git clone https://gitlab.gnome.org/GNOME/eog.git %s/git/eog' % settings.SCRATCHDIR in cmds,
-            "%s is not the expected 'git clone' command." % cmds[0]
+            "git clone https://gitlab.gnome.org/GNOME/eog.git %s/git/eog" % settings.SCRATCHDIR in cmds,
+            "%s is not the expected 'git clone' command." % cmds[0],
         )
 
     def test_head_branch(self):
-        self.assertEqual(self.mod.get_head_branch().name, 'master')
-        Branch.objects.filter(module=self.mod, name='master').delete()
-        Branch.objects.bulk_create([
-            Branch(module=self.mod, name='gnome-3-18'),
-            Branch(module=self.mod, name='gnome-41'),
-            Branch(module=self.mod, name='gnome-2-1'),
-        ])
-        self.assertEqual(self.mod.get_head_branch().name, 'gnome-41')
+        self.assertEqual(self.mod.get_head_branch().name, "master")
+        Branch.objects.filter(module=self.mod, name="master").delete()
+        Branch.objects.bulk_create(
+            [
+                Branch(module=self.mod, name="gnome-3-18"),
+                Branch(module=self.mod, name="gnome-41"),
+                Branch(module=self.mod, name="gnome-2-1"),
+            ]
+        )
+        self.assertEqual(self.mod.get_head_branch().name, "gnome-41")
 
     def test_branch_methods(self):
-        branch = Branch(module=self.mod, name='master')
+        branch = Branch(module=self.mod, name="master")
         self.assertTrue(branch.is_head())
-        self.assertEqual(
-            branch.get_vcs_web_url(),
-            "https://gitlab.gnome.org/GNOME/gnome-hello/";
-        )
-        self.assertEqual(
-            branch.get_vcs_web_log_url(),
-            'https://gitlab.gnome.org/GNOME/gnome-hello/commits/master'
-        )
-        branch.name = 'gnome-3-30'
-        self.assertEqual(
-            branch.get_vcs_web_log_url(),
-            'https://gitlab.gnome.org/GNOME/gnome-hello/commits/gnome-3-30'
-        )
+        self.assertEqual(branch.get_vcs_web_url(), "https://gitlab.gnome.org/GNOME/gnome-hello/";)
+        self.assertEqual(branch.get_vcs_web_log_url(), 
"https://gitlab.gnome.org/GNOME/gnome-hello/commits/master";)
+        branch.name = "gnome-3-30"
+        self.assertEqual(branch.get_vcs_web_log_url(), 
"https://gitlab.gnome.org/GNOME/gnome-hello/commits/gnome-3-30";)
 
     def test_branch_domains(self):
         """
@@ -167,37 +168,37 @@ class ModuleTestCase(TestCase):
         based on the domain branch_form/branch_to fields.
         """
         domains = self.branch.get_domains()
-        self.assertEqual(list(domains.keys()), ['po', 'help'])
+        self.assertEqual(list(domains.keys()), ["po", "help"])
 
-        b3 = Branch(name='gnome-3-3', module=self.mod)
+        b3 = Branch(name="gnome-3-3", module=self.mod)
         b3.save(update_statistics=False)
-        b5 = Branch(name='gnome-3-5', module=self.mod)
+        b5 = Branch(name="gnome-3-5", module=self.mod)
         b5.save(update_statistics=False)
-        b7 = Branch(name='gnome-3-7', module=self.mod)
+        b7 = Branch(name="gnome-3-7", module=self.mod)
         b7.save(update_statistics=False)
-        help_domain = domains['help']
+        help_domain = domains["help"]
         help_domain.branch_from = b5
         help_domain.save()
-        self.assertEqual(list(b3.get_domains().keys()), ['po'])
+        self.assertEqual(list(b3.get_domains().keys()), ["po"])
         self.assertFalse(b3.has_domain(help_domain))
-        self.assertTrue(b3.has_domain(domains['po']))
-        self.assertEqual(list(b5.get_domains().keys()), ['po', 'help'])
+        self.assertTrue(b3.has_domain(domains["po"]))
+        self.assertEqual(list(b5.get_domains().keys()), ["po", "help"])
         help_domain.branch_to = b7
         help_domain.save()
-        self.assertEqual(list(self.branch.get_domains().keys()), ['po'])
+        self.assertEqual(list(self.branch.get_domains().keys()), ["po"])
 
     @test_scratchdir
     def test_branch_stats(self):
-        lang = Language.objects.create(name='xxx', locale='xxx')
+        lang = Language.objects.create(name="xxx", locale="xxx")
         ghost_stat = Statistics.objects.create(
-            branch=self.branch, domain=self.mod.domain_set.get(name='po'), language=lang
+            branch=self.branch, domain=self.mod.domain_set.get(name="po"), language=lang
         )
         # Check stats
-        with PatchShellCommand(only=['git ']):
+        with PatchShellCommand(only=["git "]):
             self.branch.update_stats(force=True)
-        fr_po_stat = Statistics.objects.get(branch=self.branch, domain__name='po', language__locale='fr')
+        fr_po_stat = Statistics.objects.get(branch=self.branch, domain__name="po", language__locale="fr")
         self.assertEqual(fr_po_stat.translated(), 44)
-        self.assertEqual(fr_po_stat.translated(scope='part'), 44)
+        self.assertEqual(fr_po_stat.translated(scope="part"), 44)
         self.assertEqual(fr_po_stat.translated_words(), 131)
         self.assertEqual(fr_po_stat.full_po, fr_po_stat.part_po)
         self.assertEqual(fr_po_stat.pot_url(), "/POT/gnome-hello.master/gnome-hello.master.pot")
@@ -205,21 +206,23 @@ class ModuleTestCase(TestCase):
         self.assertEqual(fr_po_stat.po_url(reduced=True), 
"/POT/gnome-hello.master/gnome-hello.master.fr.reduced.po")
 
         # Partial translated file
-        it_po_stat = Statistics.objects.get(branch=self.branch, domain__name='po', language__locale='it')
+        it_po_stat = Statistics.objects.get(branch=self.branch, domain__name="po", language__locale="it")
         self.assertEqual(it_po_stat.translated(), 9)
         self.assertEqual(it_po_stat.full_po, it_po_stat.part_po)
 
         # Committed file with less strings that merged one.
-        de_po_stat = Statistics.objects.get(branch=self.branch, domain__name='po', language__locale='de')
+        de_po_stat = Statistics.objects.get(branch=self.branch, domain__name="po", language__locale="de")
         self.assertEqual(de_po_stat.information_set.count(), 1)
-        self.assertTrue(de_po_stat.information_set.first().description.startswith(
-            'The currently committed file has less translated strings'
-        ))
+        self.assertTrue(
+            de_po_stat.information_set.first().description.startswith(
+                "The currently committed file has less translated strings"
+            )
+        )
 
-        pot_doc_stat = Statistics.objects.get(branch=self.branch, domain__name='help', language=None)
+        pot_doc_stat = Statistics.objects.get(branch=self.branch, domain__name="help", language=None)
         self.assertEqual(pot_doc_stat.untranslated(), 43)
         self.assertEqual(len(pot_doc_stat.full_po.figures), 2)
-        fr_doc_stat = Statistics.objects.get(branch=self.branch, domain__name='help', language__locale='fr')
+        fr_doc_stat = Statistics.objects.get(branch=self.branch, domain__name="help", language__locale="fr")
         # Depending on the itstool version, the "GNOME Hello" string may or may
         # not have a msgctxt marker. Allowing both here.
         self.assertIn(fr_doc_stat.fuzzy(), [1, 2])
@@ -229,32 +232,32 @@ class ModuleTestCase(TestCase):
         with self.assertRaises(Statistics.DoesNotExist):
             Statistics.objects.get(pk=ghost_stat.pk)
 
-        stats = self.branch.get_stats('doc', mandatory_langs=[lang])
-        self.assertIn('help', stats)
-        self.assertTrue(stats['help'][0].is_pot_stats())  # POT first
-        self.assertEqual(stats['help'][-1].language.locale, 'xxx')  # Fake stat last
+        stats = self.branch.get_stats("doc", mandatory_langs=[lang])
+        self.assertIn("help", stats)
+        self.assertTrue(stats["help"][0].is_pot_stats())  # POT first
+        self.assertEqual(stats["help"][-1].language.locale, "xxx")  # Fake stat last
 
     @test_scratchdir
     def test_branch_exists(self):
-        branch = Branch.objects.get(name='master', module__name='zenity')
+        branch = Branch.objects.get(name="master", module__name="zenity")
         self.assertFalse(branch._repo.exists())
-        branch = Branch.objects.get(name='master', module__name='gnome-hello')
+        branch = Branch.objects.get(name="master", module__name="gnome-hello")
         self.assertTrue(branch._repo.exists())
 
     @test_scratchdir
     def test_rename_branch(self):
-        branch = Branch.objects.get(name='master', module__name='gnome-hello')
-        _, out, _ = run_shell_command(['git', 'branch', '--list'], cwd=branch.co_path)
-        self.assertNotIn('gnome-hello-1-4', out)
-        branch.name = 'gnome-hello-1-4'
+        branch = Branch.objects.get(name="master", module__name="gnome-hello")
+        _, out, _ = run_shell_command(["git", "branch", "--list"], cwd=branch.co_path)
+        self.assertNotIn("gnome-hello-1-4", out)
+        branch.name = "gnome-hello-1-4"
         branch.save(update_statistics=False)
         self.branch.co_path
-        _, out, _ = run_shell_command(['git', 'branch', '--list'], cwd=branch.co_path)
-        self.assertIn('gnome-hello-1-4', out)
+        _, out, _ = run_shell_command(["git", "branch", "--list"], cwd=branch.co_path)
+        self.assertIn("gnome-hello-1-4", out)
 
     @test_scratchdir
     def test_delete_branch(self):
-        """ Deleting the master branch of a git module deletes the checkout dir """
+        """Deleting the master branch of a git module deletes the checkout dir"""
         checkout_path = self.branch.co_path
         branch = Branch(module=self.mod, name="gnome-hello-1-4")
         branch.save(update_statistics=False)
@@ -266,216 +269,202 @@ class ModuleTestCase(TestCase):
     def test_create_unexisting_branch(self):
         """Creating a non-existing branch should raise a ValidationError."""
         Branch.checkout_on_creation = True
-        with patch('stats.tests.tests.Branch.checkout', side_effect=OSError()):
+        with patch("stats.tests.tests.Branch.checkout", side_effect=OSError()):
             branch = Branch(name="trunk2", module=self.mod)
             self.assertRaises(ValidationError, branch.clean)
 
     def test_edit_branches_form(self):
-        coord = User.objects.get(username='coord')
-        coord.set_password('coord')
+        coord = User.objects.get(username="coord")
+        coord.set_password("coord")
         coord.save()
-        self.client.login(username='coord', password='coord')
-        response = self.client.get(reverse('module_edit_branches', args=[self.mod]), follow=True)
+        self.client.login(username="coord", password="coord")
+        response = self.client.get(reverse("module_edit_branches", args=[self.mod]), follow=True)
         self.assertContains(response, "you're not allowed to edit")
         # Test now with appropriate permissions
         coord.is_superuser = True
         coord.save()
         # Duplicate data
-        cat_name = CategoryName.objects.get(name='Desktop')
+        cat_name = CategoryName.objects.get(name="Desktop")
         response = self.client.post(
-            reverse('module_edit_branches', args=[self.mod]), {
-                '1': '1', '1_cat': str(cat_name.pk),
-                '2': '2', '2_cat': str(cat_name.pk),
-                'new_branch': 'master', 'new_branch_release': '2', 'new_branch_category': str(cat_name.pk)
-            }
+            reverse("module_edit_branches", args=[self.mod]),
+            {
+                "1": "1",
+                "1_cat": str(cat_name.pk),
+                "2": "2",
+                "2_cat": str(cat_name.pk),
+                "new_branch": "master",
+                "new_branch_release": "2",
+                "new_branch_category": str(cat_name.pk),
+            },
         )
         self.assertContains(response, "the form is not valid")
 
     def test_branch_sorting(self):
-        b1 = Branch(name='a-branch', module=self.mod)
+        b1 = Branch(name="a-branch", module=self.mod)
         b1.save(update_statistics=False)
-        b2 = Branch(name='p-branch', module=self.mod)
+        b2 = Branch(name="p-branch", module=self.mod)
         b2.save(update_statistics=False)
-        self.assertEqual(
-            [b.name for b in sorted(self.mod.branch_set.all())],
-            ['master', 'p-branch', 'a-branch']
-        )
+        self.assertEqual([b.name for b in sorted(self.mod.branch_set.all())], ["master", "p-branch", 
"a-branch"])
         b1.weight = -1
         b1.save(update_statistics=False)
-        self.assertEqual(
-            [b.name for b in sorted(self.mod.branch_set.all())],
-            ['master', 'a-branch', 'p-branch']
-        )
-        self.assertEqual(
-            [b.name for b in self.mod.get_branches(reverse=True)],
-            ['p-branch', 'a-branch', 'master']
-        )
+        self.assertEqual([b.name for b in sorted(self.mod.branch_set.all())], ["master", "a-branch", 
"p-branch"])
+        self.assertEqual([b.name for b in self.mod.get_branches(reverse=True)], ["p-branch", "a-branch", 
"master"])
         # "Clever" sorting of alphanumeric names
-        b3 = Branch(name='gnome-3-2', module=self.mod)
+        b3 = Branch(name="gnome-3-2", module=self.mod)
         b3.save(update_statistics=False)
-        b4 = Branch(name='gnome-3-10', module=self.mod)
+        b4 = Branch(name="gnome-3-10", module=self.mod)
         b4.save(update_statistics=False)
-        b5 = Branch(name='gnome-3-20', module=self.mod)
+        b5 = Branch(name="gnome-3-20", module=self.mod)
         b5.save(update_statistics=False)
         self.assertEqual(
             [b.name for b in sorted(self.mod.branch_set.all())],
-            ['master', 'a-branch', 'p-branch', 'gnome-3-20', 'gnome-3-10', 'gnome-3-2']
+            ["master", "a-branch", "p-branch", "gnome-3-20", "gnome-3-10", "gnome-3-2"],
         )
 
     @test_scratchdir
     def test_string_frozen_mail(self):
-        """ String change for a module of a string_frozen release should generate a message """
+        """String change for a module of a string_frozen release should generate a message"""
         mail.outbox = []
-        with PatchShellCommand(only=['git ']):
+        with PatchShellCommand(only=["git "]):
             self.branch.update_stats(force=False)
 
         # Create a new file with translation
         new_file_path = self.branch.co_path / "dummy_file.py"
         new_string = "Dummy string for D-L tests"
-        with new_file_path.open('w') as fh:
+        with new_file_path.open("w") as fh:
             fh.write("a = _('%s')\n" % new_string)
         # Add the new file to POTFILES.in
-        with (self.branch.co_path / "po" / "POTFILES.in").open('a') as fh:
+        with (self.branch.co_path / "po" / "POTFILES.in").open("a") as fh:
             fh.write("dummy_file.py\n")
         # Regenerate stats (mail should be sent)
         self.branch.update_stats(force=False, checkout=False)
         # Assertions
         self.assertEqual(len(mail.outbox), 1)
         self.assertEqual(mail.outbox[0].subject, "[Damned Lies] String additions to 'gnome-hello.master'")
-        self.assertEqual(mail.outbox[0].extra_headers, {settings.EMAIL_HEADER_NAME: 'stringchange'})
+        self.assertEqual(mail.outbox[0].extra_headers, {settings.EMAIL_HEADER_NAME: "stringchange"})
         self.assertIn('"%s"' % new_string, mail.outbox[0].body)
 
     @test_scratchdir
     def test_dynamic_po(self):
-        """ Test the creation of a blank po file for a new language """
+        """Test the creation of a blank po file for a new language"""
         tamil = Language.objects.create(name="Tamil", locale="ta")
-        with PatchShellCommand(only=['git ']):
+        with PatchShellCommand(only=["git "]):
             self.branch.update_stats(force=False)  # At least POT stats needed
-        pot_stats = Statistics.objects.get(
-            branch=self.branch, domain__name='po', language__isnull=True
-        )
+        pot_stats = Statistics.objects.get(branch=self.branch, domain__name="po", language__isnull=True)
         dyn_url = FakeLangStatistics(pot_stats, tamil).po_url()
         response = self.client.get(dyn_url)
-        self.assertEqual(
-            response['content-disposition'],
-            'inline; filename=gnome-hello.master.ta.po'
-        )
-        self.assertContains(response, """# Tamil translation for gnome-hello.
+        self.assertEqual(response["content-disposition"], "inline; filename=gnome-hello.master.ta.po")
+        self.assertContains(
+            response,
+            """# Tamil translation for gnome-hello.
 # Copyright (C) %s gnome-hello's COPYRIGHT HOLDER
 # This file is distributed under the same license as the gnome-hello package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.""" % date.today().year)
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR."""
+            % date.today().year,
+        )
         self.assertContains(response, "Language-Team: Tamil <ta li org>")
 
         dyn_url = FakeLangStatistics(pot_stats, tamil).po_url(reduced=True)
         response = self.client.get(dyn_url)
-        self.assertEqual(
-            response['content-disposition'],
-            'inline; filename=gnome-hello.master.ta-reduced.po'
-        )
-        self.assertContains(response, '# Tamil translation for gnome-hello.')
+        self.assertEqual(response["content-disposition"], "inline; 
filename=gnome-hello.master.ta-reduced.po")
+        self.assertContains(response, "# Tamil translation for gnome-hello.")
 
     @test_scratchdir
     def test_dynamic_po_msginit_error(self):
         tamil = Language.objects.create(name="Tamil", locale="ta")
-        with PatchShellCommand(only=['git ']):
+        with PatchShellCommand(only=["git "]):
             self.branch.update_stats(force=False)  # At least POT stats needed
-        pot_stats = Statistics.objects.get(
-            branch=self.branch, domain__name='po', language__isnull=True
-        )
+        pot_stats = Statistics.objects.get(branch=self.branch, domain__name="po", language__isnull=True)
         dyn_url = FakeLangStatistics(pot_stats, tamil).po_url()
-        with patch('stats.views.run_shell_command', side_effect=OSError()):
+        with patch("stats.views.run_shell_command", side_effect=OSError()):
             response = self.client.get(dyn_url)
-        self.assertContains(
-            response,
-            "Unable to produce a new .po file for language ta"
-        )
+        self.assertContains(response, "Unable to produce a new .po file for language ta")
 
     @test_scratchdir
     def test_commit_ability(self):
         branch = self.mod.get_head_branch()
-        domain = self.mod.domain_set.get(name='po')
-        fr_lang = Language.objects.get(locale='fr')
+        domain = self.mod.domain_set.get(name="po")
+        fr_lang = Language.objects.get(locale="fr")
 
         with self.assertRaises(UnableToCommit):
             # read-only VCS
             domain.commit_info(branch, fr_lang)
 
         # Setup as a writable repo
-        self.mod.vcs_root = 'git gitlab gnome org:GNOME/%s.git' % self.mod.name
-        linguas = branch.co_path / 'po' / 'LINGUAS'
+        self.mod.vcs_root = "git gitlab gnome org:GNOME/%s.git" % self.mod.name
+        linguas = branch.co_path / "po" / "LINGUAS"
         os.remove(str(linguas))
-        bem_lang = Language.objects.get(locale='bem')
+        bem_lang = Language.objects.get(locale="bem")
         with self.assertRaises(UnableToCommit):
             # unaccessible LINGUAS file for a new lang
             domain.commit_info(branch, bem_lang)
 
-        domain.linguas_location = 'no'
-        self.assertEqual(
-            domain.commit_info(branch, bem_lang),
-            (branch.co_path / 'po' / 'bem.po', False, None)
-        )
+        domain.linguas_location = "no"
+        self.assertEqual(domain.commit_info(branch, bem_lang), (branch.co_path / "po" / "bem.po", False, 
None))
 
-        self.assertEqual(
-            domain.commit_info(branch, fr_lang),
-            (branch.co_path / 'po' / 'fr.po', True, None)
-        )
+        self.assertEqual(domain.commit_info(branch, fr_lang), (branch.co_path / "po" / "fr.po", True, None))
 
     @test_scratchdir
     def test_commit_po(self):
         branch = self.mod.get_head_branch()
-        po_file = Path(__file__).parent / 'test.po'
-        domain = self.mod.domain_set.get(name='po')
-        fr_lang = Language.objects.get(locale='fr')
+        po_file = Path(__file__).parent / "test.po"
+        domain = self.mod.domain_set.get(name="po")
+        fr_lang = Language.objects.get(locale="fr")
         with PatchShellCommand():
-            with self.assertRaisesRegex(UnableToCommit, 'read only'):
-                with self.assertLogs('stats.models', level='ERROR'):
-                    branch.commit_po(po_file, domain, fr_lang, 'Author <someone example org>')
+            with self.assertRaisesRegex(UnableToCommit, "read only"):
+                with self.assertLogs("stats.models", level="ERROR"):
+                    branch.commit_po(po_file, domain, fr_lang, "Author <someone example org>")
         # Setup as a writable repo
-        self.mod.vcs_root = 'git gitlab gnome org:GNOME/%s.git' % self.mod.name
+        self.mod.vcs_root = "git gitlab gnome org:GNOME/%s.git" % self.mod.name
         self.mod.save()
 
         update_repo_sequence = (
-            'git checkout -f master', 'git fetch', 'git reset --hard origin/master',
-            'git clean -dfq', 'if [ -e .gitmodules ]; then git submodule update --init; fi',
+            "git checkout -f master",
+            "git fetch",
+            "git reset --hard origin/master",
+            "git clean -dfq",
+            "if [ -e .gitmodules ]; then git submodule update --init; fi",
         )
         # User interface (existing language)
         git_ops = update_repo_sequence + (
-            'git add po/fr.po',
+            "git add po/fr.po",
             # Quoting is done at the Popen level
-            'git commit -m Update French translation --author Author <someone example org>',
-            'git push origin master', 'git log -n1 --format=oneline',
+            "git commit -m Update French translation --author Author <someone example org>",
+            "git push origin master",
+            "git log -n1 --format=oneline",
         )
         with PatchShellCommand() as cmds:
-            branch.commit_po(po_file, domain, fr_lang, 'Author <someone example org>')
+            branch.commit_po(po_file, domain, fr_lang, "Author <someone example org>")
             for idx, cmd in enumerate(git_ops):
                 self.assertIn(cmd, cmds[idx])
 
         # User interface (new language)
-        bem_lang = Language.objects.get(locale='bem')
+        bem_lang = Language.objects.get(locale="bem")
         git_ops = update_repo_sequence + (
-            'git add po/bem.po', 'git add po/LINGUAS',
-            'git commit -m Add Bemba translation --author Author <someone example org>',
-            'git push origin master'
+            "git add po/bem.po",
+            "git add po/LINGUAS",
+            "git commit -m Add Bemba translation --author Author <someone example org>",
+            "git push origin master",
         )
         with PatchShellCommand() as cmds:
-            with self.assertLogs('stats.models', level='ERROR'):
-                branch.commit_po(po_file, domain, bem_lang, 'Author <someone example org>')
+            with self.assertLogs("stats.models", level="ERROR"):
+                branch.commit_po(po_file, domain, bem_lang, "Author <someone example org>")
             for idx, cmd in enumerate(git_ops):
                 self.assertIn(cmd, cmds[idx])
-            self.assertIn('intltool-update -g', cmds[-1])
+            self.assertIn("intltool-update -g", cmds[-1])
 
         # Check lang has been inserted in LINGUAS file, before 'de'
-        self.assertIn('bem\nde', (branch.domain_path(domain) / 'LINGUAS').read_text())
+        self.assertIn("bem\nde", (branch.domain_path(domain) / "LINGUAS").read_text())
 
         # Documentation
-        domain = self.mod.domain_set.get(name='help')
+        domain = self.mod.domain_set.get(name="help")
         git_ops = update_repo_sequence + (
-            'git add help/fr/fr.po',
-            'git commit -m Update French translation --author Author <someone example org>',
-            'git push origin master'
+            "git add help/fr/fr.po",
+            "git commit -m Update French translation --author Author <someone example org>",
+            "git push origin master",
         )
         with PatchShellCommand() as cmds:
-            branch.commit_po(po_file, domain, fr_lang, 'Author <someone example org>')
+            branch.commit_po(po_file, domain, fr_lang, "Author <someone example org>")
             for idx, cmd in enumerate(git_ops):
                 self.assertIn(cmd, cmds[idx])
 
@@ -486,42 +475,47 @@ class ModuleTestCase(TestCase):
         cherry-pick the branch commit on the master branch.
         """
         # Setup as a writable repo
-        self.mod.vcs_root = 'git gitlab gnome org:GNOME/%s.git' % self.mod.name
+        self.mod.vcs_root = "git gitlab gnome org:GNOME/%s.git" % self.mod.name
         self.mod.save()
-        domain = self.mod.domain_set.get(name='po')
+        domain = self.mod.domain_set.get(name="po")
         commit_dir = self.branch.co_path
         run_shell_command(
-            ['git', 'config', 'user.name', 'John Doe'], cwd=commit_dir,
+            ["git", "config", "user.name", "John Doe"],
+            cwd=commit_dir,
         )
         run_shell_command(
-            ['git', 'config', 'user.email', 'john example org'], cwd=commit_dir,
+            ["git", "config", "user.email", "john example org"],
+            cwd=commit_dir,
         )
         run_shell_command(
-            ['git', 'checkout', '-b', 'gnome-3-18', 'origin/master'], cwd=commit_dir, raise_on_error=True
+            ["git", "checkout", "-b", "gnome-3-18", "origin/master"], cwd=commit_dir, raise_on_error=True
         )
         branch = Branch(module=self.mod, name="gnome-3-18")
         branch.save(update_statistics=False)
-        fr_lang = Language.objects.get(locale='fr')
+        fr_lang = Language.objects.get(locale="fr")
         # Copy stats from master
         stat = Statistics.objects.get(language=fr_lang, branch=self.branch, domain=domain)
         stat.pk, stat.full_po, stat.part_po = None, None, None
         stat.branch = branch
         stat.save()
-        po_file = Path(__file__).parent / 'test.po'
-        self.mod.vcs_root = self.mod.vcs_root.replace('git://', 'ssh://')
+        po_file = Path(__file__).parent / "test.po"
+        self.mod.vcs_root = self.mod.vcs_root.replace("git://", "ssh://")
         self.mod.save()
 
-        with PatchShellCommand(only=['git push', 'git fetch', 'git reset']) as cmds:
-            commit_hash = branch.commit_po(po_file, domain, fr_lang, 'Piotr Drąg <piotr example org>')
+        with PatchShellCommand(only=["git push", "git fetch", "git reset"]) as cmds:
+            commit_hash = branch.commit_po(po_file, domain, fr_lang, "Piotr Drąg <piotr example org>")
         update_repo_sequence = (
-            'git checkout -f master', 'git fetch', 'git reset --hard origin/master',
-            'git clean -dfq', 'if [ -e .gitmodules ]; then git submodule update --init; fi',
+            "git checkout -f master",
+            "git fetch",
+            "git reset --hard origin/master",
+            "git clean -dfq",
+            "if [ -e .gitmodules ]; then git submodule update --init; fi",
         )
         git_ops = update_repo_sequence + (
-            'git cherry-pick -x',
-            'git push origin master',
+            "git cherry-pick -x",
+            "git push origin master",
         )
-        with PatchShellCommand(only=['git push', 'git fetch', 'git reset']) as cmds:
+        with PatchShellCommand(only=["git push", "git fetch", "git reset"]) as cmds:
             self.branch.cherrypick_commit(commit_hash, domain)
             for idx, cmd in enumerate(git_ops):
                 self.assertIn(cmd, cmds[idx])
@@ -534,20 +528,22 @@ class ModuleTestCase(TestCase):
 
     @test_scratchdir
     def test_update_maintainers_from_doap_file(self):
-        from stats.doap import update_doap_infos
         from people.models import Person
+        from stats.doap import update_doap_infos
+
         # Add a maintainer which will be removed
         pers = Person(username="toto")
         pers.save()
         self.mod.maintainers.add(pers)
         update_doap_infos(self.mod)
         self.assertEqual(self.mod.maintainers.count(), 3)
-        claude = self.mod.maintainers.get(email='claude 2xlibre net')
-        self.assertEqual(claude.username, 'claudep')
+        claude = self.mod.maintainers.get(email="claude 2xlibre net")
+        self.assertEqual(claude.username, "claudep")
 
     @test_scratchdir
     def test_update_doap_infos(self):
         from stats.doap import update_doap_infos
+
         update_doap_infos(self.mod)
         self.assertEqual(self.mod.homepage, "http://git.gnome.org/browse/gnome-hello";)
 
@@ -559,15 +555,16 @@ class TestModuleBase(TestCase):
         super().setUpTestData()
         Branch.checkout_on_creation = False
         cls.mod = Module.objects.create(
-            name="testmodule", vcs_type='git',
-            bugs_base='https://gitlab.gnome.org/GNOME/testmodule/issues',
+            name="testmodule",
+            vcs_type="git",
+            bugs_base="https://gitlab.gnome.org/GNOME/testmodule/issues";,
         )
         cls.branch = Branch(module=cls.mod, name="master")
         cls.branch.save(update_statistics=False)
 
     @classmethod
     def tearDownClass(cls):
-        html_dir = settings.SCRATCHDIR / 'HTML'
+        html_dir = settings.SCRATCHDIR / "HTML"
         if html_dir.exists():
             shutil.rmtree(str(html_dir))
         super().tearDownClass()
@@ -580,56 +577,67 @@ class DomainTests(TestModuleBase):
 
     def test_get_po_path(self):
         # Standard UI
-        dom = Domain(module=self.mod, dtype='ui', layout='po/{lang}.po')
-        self.assertEqual(dom.get_po_path('fr'), 'po/fr.po')
+        dom = Domain(module=self.mod, dtype="ui", layout="po/{lang}.po")
+        self.assertEqual(dom.get_po_path("fr"), "po/fr.po")
         # Standard Doc
-        dom.dtype = 'doc'
-        dom.layout = 'help/{lang}/{lang}.po'
-        self.assertEqual(dom.get_po_path('fr'), 'help/fr/fr.po')
+        dom.dtype = "doc"
+        dom.layout = "help/{lang}/{lang}.po"
+        self.assertEqual(dom.get_po_path("fr"), "help/fr/fr.po")
 
     def test_domain_linguas(self):
-        help_domain = Domain(
-            module=self.mod, name='help', dtype='doc', layout='help_mallard/{lang}/{lang}.po'
-        )
+        help_domain = Domain(module=self.mod, name="help", dtype="doc", 
layout="help_mallard/{lang}/{lang}.po")
         help_domain.linguas_location = "help_mallard/Makefile.am"
-        self.assertEqual(help_domain.get_linguas(self.branch), {
-            'langs': None,
-            'error': 'Entry for this language is not present in ALL_LINGUAS variable in '
-                     'help_mallard/Makefile.am file.',
-        })
+        self.assertEqual(
+            help_domain.get_linguas(self.branch),
+            {
+                "langs": None,
+                "error": "Entry for this language is not present in ALL_LINGUAS variable in "
+                "help_mallard/Makefile.am file.",
+            },
+        )
         help_domain.linguas_location = "help_mallard/Makefile.am#HELP_LINGUAS"
-        self.assertEqual(help_domain.get_linguas(self.branch), {
-            'langs': ['es', 'fr'],
-            'error': 'Entry for this language is not present in HELP_LINGUAS variable in '
-                     'help_mallard/Makefile.am file.',
-        })
-        help_domain.linguas_location = ''
-        self.assertEqual(help_domain.get_linguas(self.branch), {
-            'langs': ['es', 'fr'],
-            'error': 'DOC_LINGUAS list doesn’t include this language.',
-        })
+        self.assertEqual(
+            help_domain.get_linguas(self.branch),
+            {
+                "langs": ["es", "fr"],
+                "error": "Entry for this language is not present in HELP_LINGUAS variable in "
+                "help_mallard/Makefile.am file.",
+            },
+        )
+        help_domain.linguas_location = ""
+        self.assertEqual(
+            help_domain.get_linguas(self.branch),
+            {
+                "langs": ["es", "fr"],
+                "error": "DOC_LINGUAS list doesn’t include this language.",
+            },
+        )
 
     def test_domain_xgettext_command(self):
-        domain = Domain(
-            module=self.mod, name='po', dtype='ui', layout='po/{lang}.po'
-        )
+        domain = Domain(module=self.mod, name="po", dtype="ui", layout="po/{lang}.po")
         self.assertEqual(
             domain.get_xgettext_command(self.branch),
             (
                 [
-                    'xgettext', '--files-from', 'POTFILES.in', '--directory',
-                    str(settings.SCRATCHDIR / 'git' / 'testmodule'),
-                    '--from-code', 'utf-8',
-                    '--add-comments', '--output', 'testmodule.pot'
+                    "xgettext",
+                    "--files-from",
+                    "POTFILES.in",
+                    "--directory",
+                    str(settings.SCRATCHDIR / "git" / "testmodule"),
+                    "--from-code",
+                    "utf-8",
+                    "--add-comments",
+                    "--output",
+                    "testmodule.pot",
                 ]
-                + list(GLIB_PRESET) +
-                [
-                    '--keyword=Description',
-                    '--msgid-bugs-address',
-                    'https://gitlab.gnome.org/GNOME/testmodule/issues'
+                + list(GLIB_PRESET)
+                + [
+                    "--keyword=Description",
+                    "--msgid-bugs-address",
+                    "https://gitlab.gnome.org/GNOME/testmodule/issues";,
                 ],
-                {'GETTEXTDATADIRS': os.path.dirname(utils.ITS_DATA_DIR)}
-            )
+                {"GETTEXTDATADIRS": os.path.dirname(utils.ITS_DATA_DIR)},
+            ),
         )
 
     def test_doc_domain_docbook(self):
@@ -637,50 +645,50 @@ class DomainTests(TestModuleBase):
         Test Docbook-style help
         """
         domain = Domain.objects.create(
-            module=self.mod, name='release-notes', dtype='doc',
-            layout='help_docbook/{lang}/{lang}.po'
+            module=self.mod, name="release-notes", dtype="doc", layout="help_docbook/{lang}/{lang}.po"
         )
         potfile, errs = utils.generate_doc_pot_file(self.branch, domain)
         self.assertEqual(errs, [])
         doc_format = domain.doc_format(self.branch)
-        self.assertEqual(doc_format.format, 'docbook')
+        self.assertEqual(doc_format.format, "docbook")
         pot_path = self.branch.domain_path(domain) / "C" / "release-notes.pot"
         self.assertTrue(pot_path.exists())
         self.addCleanup(os.remove, str(pot_path))
-        self.assertIn('#: C/rnlookingforward.xml:11', pot_path.read_text())
+        self.assertIn("#: C/rnlookingforward.xml:11", pot_path.read_text())
         res = utils.get_fig_stats(pot_path, doc_format=doc_format)
         self.assertEqual(len(res), 1)
-        self.assertEqual(res[0]['path'], "figures/rnusers.nautilus.png")
+        self.assertEqual(res[0]["path"], "figures/rnusers.nautilus.png")
 
     def test_doc_domain_mallard(self):
         """
         Test Mallard-style help (with itstool)
         """
         domain = Domain.objects.create(
-            module=self.mod, name='release-notes', dtype='doc',
-            layout='help_mallard/{lang}/{lang}.po'
+            module=self.mod, name="release-notes", dtype="doc", layout="help_mallard/{lang}/{lang}.po"
         )
         potfile, errs = utils.generate_doc_pot_file(self.branch, domain)
         self.assertEqual(errs, [])
         doc_format = domain.doc_format(self.branch)
-        self.assertEqual(potfile.name, 'release-notes.pot')
-        self.assertEqual(doc_format.tool, 'itstool')
-        self.assertEqual(doc_format.format, 'mallard')
+        self.assertEqual(potfile.name, "release-notes.pot")
+        self.assertEqual(doc_format.tool, "itstool")
+        self.assertEqual(doc_format.format, "mallard")
         self.addCleanup(os.remove, str(potfile))
-        self.assertIn('#: C/what-is.page:7', potfile.read_text())
+        self.assertIn("#: C/what-is.page:7", potfile.read_text())
         res = utils.get_fig_stats(potfile, doc_format=doc_format)
         self.assertEqual(len(res), 1)
-        self.assertEqual(res[0]['path'], "figures/gnome-hello-logo.png")
-        self.assertEqual(res[0]['hash'], "1ae47b7a7c4fbeb1f6bb72c0cf18d389")
+        self.assertEqual(res[0]["path"], "figures/gnome-hello-logo.png")
+        self.assertEqual(res[0]["hash"], "1ae47b7a7c4fbeb1f6bb72c0cf18d389")
 
     def test_http_pot(self):
         dom = Domain.objects.create(
-            module=self.mod, name='http-po', dtype='ui',
-            pot_method='url',
-            pot_params='https://l10n.gnome.org/POT/damned-lies.master/damned-lies.master.pot'
+            module=self.mod,
+            name="http-po",
+            dtype="ui",
+            pot_method="url",
+            pot_params="https://l10n.gnome.org/POT/damned-lies.master/damned-lies.master.pot";,
         )
-        with patch('stats.models.request.urlopen') as mock_urlopen:
-            with open(Path(__file__).parent / 'empty.pot', mode='rb') as fh:
+        with patch("stats.models.request.urlopen") as mock_urlopen:
+            with open(Path(__file__).parent / "empty.pot", mode="rb") as fh:
                 mock_urlopen.return_value = fh
                 potfile, errs = dom.generate_pot_file(self.branch)
         self.addCleanup(os.remove, str(potfile))
@@ -688,9 +696,7 @@ class DomainTests(TestModuleBase):
 
     def test_shell_pot(self):
         dom = Domain.objects.create(
-            module=self.mod, name='shell-po', dtype='ui',
-            pot_method='shell',
-            pot_params='touch some.pot'
+            module=self.mod, name="shell-po", dtype="ui", pot_method="shell", pot_params="touch some.pot"
         )
         potfile, errs = dom.generate_pot_file(self.branch)
         self.addCleanup(os.remove, str(potfile))
@@ -699,21 +705,27 @@ class DomainTests(TestModuleBase):
     @skipUnless(has_translate_subtitle_support, "This test needs translate-toolkit subtitles support.")
     def test_subtitles_pot(self):
         dom = Domain.objects.create(
-            module=self.mod, name='po', dtype='ui', layout='subtitles/po/{lang}.po',
-            pot_method='subtitles',
+            module=self.mod,
+            name="po",
+            dtype="ui",
+            layout="subtitles/po/{lang}.po",
+            pot_method="subtitles",
         )
         potfile, errs = dom.generate_pot_file(self.branch)
         self.addCleanup(os.remove, str(potfile))
         self.assertTrue(potfile.exists())
         self.assertEqual(
             dom.get_lang_files(self.branch.co_path),
-            [('es', Path('%s/git/testmodule/subtitles/po/es.po' % settings.SCRATCHDIR))]
+            [("es", Path("%s/git/testmodule/subtitles/po/es.po" % settings.SCRATCHDIR))],
         )
 
     def test_repository_pot(self):
         dom = Domain.objects.create(
-            module=self.mod, name='po', dtype='ui', layout='inrepo/{lang}.po',
-            pot_method='in_repo',
+            module=self.mod,
+            name="po",
+            dtype="ui",
+            layout="inrepo/{lang}.po",
+            pot_method="in_repo",
         )
         potfile, errs = dom.generate_pot_file(self.branch)
         self.assertEqual(errs, ())
@@ -721,41 +733,40 @@ class DomainTests(TestModuleBase):
 
 
 class StatisticsTests(TestCase):
-    fixtures = ['sample_data.json']
+    fixtures = ["sample_data.json"]
 
     def test_get_global_stats(self):
         rel = Release.objects.get(name="gnome-3-8")
         stats = rel.get_global_stats()
         self.assertEqual(len(stats), 2)  # data for French and Italian
         fr_stats = stats[0]
-        self.assertEqual(fr_stats['lang_name'], 'French')
-        self.assertEqual(fr_stats['ui']['translated'], 183)
-        self.assertEqual(fr_stats['doc']['untranslated'], 259)
+        self.assertEqual(fr_stats["lang_name"], "French")
+        self.assertEqual(fr_stats["ui"]["translated"], 183)
+        self.assertEqual(fr_stats["doc"]["untranslated"], 259)
 
     def test_total_stats_for_lang(self):
         rel = Release.objects.get(name="gnome-3-8")
-        total_for_lang = rel.total_for_lang(Language.objects.get(locale='fr'))
-        self.assertEqual(total_for_lang['ui']['total'], total_for_lang['ui_part']['total'])
-        self.assertTrue(total_for_lang['ui']['untranslated'] == total_for_lang['ui_part']['untranslated'] == 
0)
-        total_for_lang = rel.total_for_lang(Language.objects.get(locale='bem'))
-        self.assertEqual(total_for_lang['ui']['total'] - 8, total_for_lang['ui_part']['total'])
-        self.assertEqual(total_for_lang['ui']['untranslated'], 183)
-        self.assertEqual(total_for_lang['ui_part']['untranslated'], 175)
+        total_for_lang = rel.total_for_lang(Language.objects.get(locale="fr"))
+        self.assertEqual(total_for_lang["ui"]["total"], total_for_lang["ui_part"]["total"])
+        self.assertTrue(total_for_lang["ui"]["untranslated"] == total_for_lang["ui_part"]["untranslated"] == 
0)
+        total_for_lang = rel.total_for_lang(Language.objects.get(locale="bem"))
+        self.assertEqual(total_for_lang["ui"]["total"] - 8, total_for_lang["ui_part"]["total"])
+        self.assertEqual(total_for_lang["ui"]["untranslated"], 183)
+        self.assertEqual(total_for_lang["ui_part"]["untranslated"], 175)
         # Test that excluded domains are taken into account
-        zenity_po = Domain.objects.get(module__name='zenity', name='po')
-        zenity_po.branch_from = Branch.objects.get(module__name='zenity', name='master')
+        zenity_po = Domain.objects.get(module__name="zenity", name="po")
+        zenity_po.branch_from = Branch.objects.get(module__name="zenity", name="master")
         zenity_po.save()
         rel = Release.objects.get(name="gnome-3-8")
-        total_for_lang = rel.total_for_lang(Language.objects.get(locale='bem'))
-        self.assertLess(total_for_lang['ui']['untranslated'], 183)
+        total_for_lang = rel.total_for_lang(Language.objects.get(locale="bem"))
+        self.assertLess(total_for_lang["ui"]["untranslated"], 183)
 
     @test_scratchdir
     def test_update_po_file(self):
         stats = Statistics.objects.get(
-            branch__module__name='zenity', branch__name='gnome-3-8',
-            domain__name='po', language__locale='it'
+            branch__module__name="zenity", branch__name="gnome-3-8", domain__name="po", language__locale="it"
         )
-        stats.full_po = PoFile.objects.create(path='git/gnome-hello/po/it.po')
+        stats.full_po = PoFile.objects.create(path="git/gnome-hello/po/it.po")
         stats.save()
         stats.full_po.update_stats()
         self.assertEqual(stats.full_po.translated, 20)
@@ -763,57 +774,56 @@ class StatisticsTests(TestCase):
 
     def test_stats_links(self):
         pot_stats = Statistics.objects.get(
-            branch__module__name='zenity', branch__name='gnome-3-8',
-            domain__name='po', language__isnull=True)
+            branch__module__name="zenity", branch__name="gnome-3-8", domain__name="po", language__isnull=True
+        )
         self.assertEqual(pot_stats.po_url(), "/POT/zenity.gnome-3-8/zenity.gnome-3-8.pot")
         stats = Statistics.objects.get(
-            branch__module__name='zenity', branch__name='gnome-3-8',
-            domain__name='po', language__locale='it')
+            branch__module__name="zenity", branch__name="gnome-3-8", domain__name="po", language__locale="it"
+        )
         self.assertEqual(stats.po_url(), "/POT/zenity.gnome-3-8/zenity.gnome-3-8.it.po")
         self.assertEqual(stats.po_url(reduced=True), "/POT/zenity.gnome-3-8/zenity.gnome-3-8.it.reduced.po")
         # Same for a fake stats
-        stats = FakeLangStatistics(pot_stats, Language.objects.get(locale='bem'))
+        stats = FakeLangStatistics(pot_stats, Language.objects.get(locale="bem"))
         self.assertEqual(stats.po_url(), "/module/po/zenity/gnome-3-8/po/bem.po")
         self.assertEqual(stats.po_url(reduced=True), "/module/po/zenity/gnome-3-8/po/bem-reduced.po")
 
     def test_total_by_releases(self):
-        releases = list(Release.objects.filter(name__in=('gnome-3-8', 'gnome-dev')))
-        stats = Release.total_by_releases('ui', releases)
-        self.assertEqual(set(stats.keys()), {'fr', 'it'})
-        self.assertEqual(stats['fr']['diff'], 0)
-        self.assertEqual(stats['fr']['stats'], [100, 100])
-        self.assertEqual(stats['it']['diff'], 24)
-        self.assertEqual(stats['it']['stats'], [63, 87])
+        releases = list(Release.objects.filter(name__in=("gnome-3-8", "gnome-dev")))
+        stats = Release.total_by_releases("ui", releases)
+        self.assertEqual(set(stats.keys()), {"fr", "it"})
+        self.assertEqual(stats["fr"]["diff"], 0)
+        self.assertEqual(stats["fr"]["stats"], [100, 100])
+        self.assertEqual(stats["it"]["diff"], 24)
+        self.assertEqual(stats["it"]["stats"], [63, 87])
 
     def test_get_lang_stats_by_type(self):
-        mod = Module.objects.get(name='zenity')
-        lang = Language.objects.get(locale='fr')
-        dom = Domain.objects.create(
-            module=mod, name='po2', dtype='ui', description='UI Translations'
-        )
-        branch = mod.branch_set.get(name='gnome-3-8')
+        mod = Module.objects.get(name="zenity")
+        lang = Language.objects.get(locale="fr")
+        dom = Domain.objects.create(module=mod, name="po2", dtype="ui", description="UI Translations")
+        branch = mod.branch_set.get(name="gnome-3-8")
         # Add one more stat so as comparison happens on two real stat objects
-        Statistics.objects.create(
-            branch=branch,
-            domain=dom,
-            language=None
-        )
-        stats = Statistics.get_lang_stats_by_type(
-            lang, 'ui', Release.objects.get(name="gnome-3-8")
-        )
+        Statistics.objects.create(branch=branch, domain=dom, language=None)
+        stats = Statistics.get_lang_stats_by_type(lang, "ui", Release.objects.get(name="gnome-3-8"))
         for key, value in {
-            'total': 183, 'totaltrans': 183, 'totalfuzzy': 0, 'totaluntrans': 0,
-            'totaltransperc': 100, 'totalfuzzyperc': 0, 'totaluntransperc': 0,
-            'dtype': 'ui', 'all_errors': []
+            "total": 183,
+            "totaltrans": 183,
+            "totalfuzzy": 0,
+            "totaluntrans": 0,
+            "totaltransperc": 100,
+            "totalfuzzyperc": 0,
+            "totaluntransperc": 0,
+            "dtype": "ui",
+            "all_errors": [],
         }.items():
             self.assertEqual(stats[key], value)
 
     def _test_update_statistics(self):
         # Temporarily deactivated, since update_stats cannot receive stats any more
         from vertimus.models import State, StateTranslating
+
         # Get a stat that has full_po and part_po
         stat = Statistics.objects.get(
-            branch__module__name='zenity', branch__name='gnome-2-30', language__locale='it', 
domain__dtype='ui'
+            branch__module__name="zenity", branch__name="gnome-2-30", language__locale="it", 
domain__dtype="ui"
         )
         pers = Person.objects.create(username="toto")
         state = StateTranslating.objects.create(
@@ -831,27 +841,25 @@ class StatisticsTests(TestCase):
     def test_information_comparable(self):
         infos = [
             Information.objects.create(
-                statistics=Statistics.objects.filter(branch__module__name='zenity').first(),
-                type='info',
+                statistics=Statistics.objects.filter(branch__module__name="zenity").first(),
+                type="info",
                 description="Some information",
             ),
             Information.objects.create(
-                statistics=Statistics.objects.filter(branch__module__name='gnome-hello').first(),
-                type='info',
+                statistics=Statistics.objects.filter(branch__module__name="gnome-hello").first(),
+                type="info",
                 description="Other information",
             ),
         ]
-        self.assertEqual(sorted(infos)[0].statistics.branch.module.name, 'gnome-hello')
+        self.assertEqual(sorted(infos)[0].statistics.branch.module.name, "gnome-hello")
 
     @test_scratchdir
     def test_empty_pot(self):
-        pot_file = Path(__file__).parent / 'empty.pot'
-        shutil.copyfile(pot_file, settings.SCRATCHDIR / 'POT' / 'zenity.master')
-        mod = Module.objects.get(name='zenity')
-        dom = Domain.objects.create(
-            module=mod, name='po2', dtype='ui', description='UI Translations'
-        )
-        branch = mod.branch_set.get(name='gnome-3-8')
+        pot_file = Path(__file__).parent / "empty.pot"
+        shutil.copyfile(pot_file, settings.SCRATCHDIR / "POT" / "zenity.master")
+        mod = Module.objects.get(name="zenity")
+        dom = Domain.objects.create(module=mod, name="po2", dtype="ui", description="UI Translations")
+        branch = mod.branch_set.get(name="gnome-3-8")
         stat = Statistics.objects.create(branch=branch, domain=dom, language=None)
         stat.update_stats(pot_file)
         self.assertEqual(stat.full_po, stat.part_po)
@@ -860,57 +868,56 @@ class StatisticsTests(TestCase):
         self.assertEqual(stat.untranslated(), 0)
 
     def test_compare_releases(self):
-        response = self.client.get(reverse('release-compare', args=['ui', '3-8/dev']))
+        response = self.client.get(reverse("release-compare", args=["ui", "3-8/dev"]))
         self.assertContains(response, f'<h1>{_("Releases Comparison")}</h1>')
-        self.assertEqual(len(response.context['releases']), 2)
+        self.assertEqual(len(response.context["releases"]), 2)
         # wrong domain type
-        with self.assertLogs('django.request', 'WARNING'):
-            response = self.client.get(reverse('release-compare', args=['whatever', '3-8/dev']))
+        with self.assertLogs("django.request", "WARNING"):
+            response = self.client.get(reverse("release-compare", args=["whatever", "3-8/dev"]))
         self.assertEqual(response.status_code, 404)
         # wrong release names
-        with self.assertLogs('django.request', 'WARNING'):
-            response = self.client.get(reverse('release-compare', args=['ui', 'whatever1/whatever2']))
+        with self.assertLogs("django.request", "WARNING"):
+            response = self.client.get(reverse("release-compare", args=["ui", "whatever1/whatever2"]))
         self.assertEqual(response.status_code, 404)
 
 
 class FigureTests(TestCase):
-    fixtures = ['sample_data.json']
+    fixtures = ["sample_data.json"]
 
     def test_figure_view(self):
-        url = reverse('docimages', args=['gnome-hello', 'help', 'master', 'fr'])
+        url = reverse("docimages", args=["gnome-hello", "help", "master", "fr"])
         response = self.client.get(url)
         self.assertContains(response, "gnome-hello-new.png")
         # Same for a non-existing language
-        Language.objects.create(name='Afrikaans', locale='af')
-        url = reverse('docimages', args=['gnome-hello', 'help', 'master', 'af'])
+        Language.objects.create(name="Afrikaans", locale="af")
+        url = reverse("docimages", args=["gnome-hello", "help", "master", "af"])
         response = self.client.get(url)
         self.assertContains(response, "gnome-hello-new.png")
 
     def test_figure_urls(self):
-        """ Test if figure urls are properly constructed """
+        """Test if figure urls are properly constructed"""
         stat = Statistics.objects.get(
-            branch__module__name='gnome-hello', branch__name='master', domain__dtype='doc', 
language__locale='fr'
+            branch__module__name="gnome-hello", branch__name="master", domain__dtype="doc", 
language__locale="fr"
         )
         figs = stat.get_figures()
         self.assertEqual(
-            figs[0]['orig_remote_url'],
-            'https://gitlab.gnome.org/GNOME/gnome-hello/raw/master/help/C/figures/gnome-hello-new.png'
+            figs[0]["orig_remote_url"],
+            "https://gitlab.gnome.org/GNOME/gnome-hello/raw/master/help/C/figures/gnome-hello-new.png";,
         )
-        self.assertFalse('trans_remote_url' in figs[0])
+        self.assertFalse("trans_remote_url" in figs[0])
 
     @test_scratchdir
     def test_identical_figure_warning(self):
-        """ Detect warning if translated figure is identical to original figure """
-        branch = Branch.objects.get(module__name='gnome-hello', name='master')
-        pot_stat = Statistics.objects.get(branch=branch, domain__name='help', language__isnull=True)
-        fig_path = str(
-            pot_stat.branch.co_path / pot_stat.domain.base_dir / 'C' /
-            pot_stat.get_figures()[0]['path']
-        )
-        shutil.copyfile(fig_path, fig_path.replace('/C/', '/fr/'))
-        doc_stat = Statistics.objects.get(branch=branch, domain__name='help', language__locale='fr')
+        """Detect warning if translated figure is identical to original figure"""
+        branch = Branch.objects.get(module__name="gnome-hello", name="master")
+        pot_stat = Statistics.objects.get(branch=branch, domain__name="help", language__isnull=True)
+        fig_path = str(pot_stat.branch.co_path / pot_stat.domain.base_dir / "C" / 
pot_stat.get_figures()[0]["path"])
+        shutil.copyfile(fig_path, fig_path.replace("/C/", "/fr/"))
+        doc_stat = Statistics.objects.get(branch=branch, domain__name="help", language__locale="fr")
         errs = utils.check_identical_figures(
-            doc_stat.get_figures(), branch.co_path / 'help', 'fr',
+            doc_stat.get_figures(),
+            branch.co_path / "help",
+            "fr",
         )
         self.assertEqual(len(errs), 1)
         self.assertTrue(errs[0][1].startswith("Figures should not be copied"))
@@ -919,143 +926,137 @@ class FigureTests(TestCase):
 class UtilsTests(TestModuleBase):
     def test_read_makefile_variable(self):
         domain = Domain.objects.create(
-            module=self.mod, name='help', dtype='doc', layout='help_docbook/{lang}/{lang}.po'
+            module=self.mod, name="help", dtype="doc", layout="help_docbook/{lang}/{lang}.po"
         )
-        makefile = utils.MakefileWrapper.find_file(
-            self.branch, [self.branch.domain_path(domain)])
+        makefile = utils.MakefileWrapper.find_file(self.branch, [self.branch.domain_path(domain)])
         var_content = makefile.read_variable("DOC_INCLUDES")
-        self.assertEqual(var_content.split(), ['rnusers.xml', 'rnlookingforward.xml', '$(NULL)'])
+        self.assertEqual(var_content.split(), ["rnusers.xml", "rnlookingforward.xml", "$(NULL)"])
 
         var_content = makefile.read_variable("HELP_FILES")
-        self.assertEqual(var_content.split(), ['rnusers.xml', 'rnlookingforward.xml'])
+        self.assertEqual(var_content.split(), ["rnusers.xml", "rnlookingforward.xml"])
 
         # Test $(top_srcdir) replacement
         var_content = makefile.read_variable("SOME_VAR")
-        self.assertEqual(var_content, str(self.branch.co_path) + '/some_path.po')
+        self.assertEqual(var_content, str(self.branch.co_path) + "/some_path.po")
 
     def test_read_meson_variables(self):
         domain = Domain.objects.create(
-            module=self.mod, name='help', dtype='doc',
-            layout='help_mallard_meson/{lang}/{lang}.po'
+            module=self.mod, name="help", dtype="doc", layout="help_mallard_meson/{lang}/{lang}.po"
         )
         meson_file = utils.MakefileWrapper.find_file(self.branch, [self.branch.domain_path(domain)])
-        self.assertEqual(meson_file.read_variable('yelp.languages'), ['es', 'fr'])
-        self.assertEqual(
-            meson_file.read_variable('yelp.sources'),
-            ['index.page', 'what-is.page', 'legal.xml']
-        )
+        self.assertEqual(meson_file.read_variable("yelp.languages"), ["es", "fr"])
+        self.assertEqual(meson_file.read_variable("yelp.sources"), ["index.page", "what-is.page", 
"legal.xml"])
         # UI meson file
         path = Path(__file__).parent / "meson-ui.build"
         meson_file = utils.MesonfileWrapper(self.branch, path)
-        self.assertEqual(meson_file.read_variable('gettext.preset'), 'glib')
-        self.assertEqual(meson_file.read_variable('gettext.args'), ['--keyword=Description'])
+        self.assertEqual(meson_file.read_variable("gettext.preset"), "glib")
+        self.assertEqual(meson_file.read_variable("gettext.args"), ["--keyword=Description"])
 
         # UI meson file (with data_dirs)
         path = Path(__file__).parent / "meson-ui2.build"
         meson_file = utils.MesonfileWrapper(self.branch, path)
         self.assertEqual(
-            meson_file.read_variable('gettext.data_dirs'),
-            [str(self.branch.co_path / 'src/gstyle/data'),
-             str(self.branch.co_path / 'data/style-schemes')]
+            meson_file.read_variable("gettext.data_dirs"),
+            [str(self.branch.co_path / "src/gstyle/data"), str(self.branch.co_path / "data/style-schemes")],
         )
 
     def test_read_cmake_variables(self):
         domain = Domain.objects.create(
-            module=self.mod, name='help', dtype='doc', layout='help_mallard/{lang}/{lang}.po'
+            module=self.mod, name="help", dtype="doc", layout="help_mallard/{lang}/{lang}.po"
         )
         cmake_file = utils.MakefileWrapper.find_file(
-            self.branch, [self.branch.domain_path(domain)], file_name='CMakeLists.txt')
-        self.assertEqual(
-            cmake_file.read_variable('HELP_FILES').split(),
-            ['index.page', 'what-is.page', 'legal.xml']
+            self.branch, [self.branch.domain_path(domain)], file_name="CMakeLists.txt"
         )
+        self.assertEqual(cmake_file.read_variable("HELP_FILES").split(), ["index.page", "what-is.page", 
"legal.xml"])
 
     def test_doc_format_source_files(self):
         domain = Domain.objects.create(
-            module=self.mod, name='help', dtype='doc', layout='help_mallard_meson/{lang}/{lang}.po'
+            module=self.mod, name="help", dtype="doc", layout="help_mallard_meson/{lang}/{lang}.po"
         )
         df = utils.DocFormat(domain, self.branch)
-        self.assertEqual(
-            df.source_files(),
-            [Path('C/index.page'), Path('C/what-is.page'), Path('C/legal.xml')]
-        )
+        self.assertEqual(df.source_files(), [Path("C/index.page"), Path("C/what-is.page"), 
Path("C/legal.xml")])
 
     @test_scratchdir
     def test_insert_locale_in_linguas(self):
-        linguas_path = settings.SCRATCHDIR / 'git' / 'gnome-hello' / 'po' / 'LINGUAS'
-        utils.insert_locale_in_linguas(linguas_path, 'xx')
-        self.assertTrue(linguas_path.read_text().endswith('\nxx\n'))
+        linguas_path = settings.SCRATCHDIR / "git" / "gnome-hello" / "po" / "LINGUAS"
+        utils.insert_locale_in_linguas(linguas_path, "xx")
+        self.assertTrue(linguas_path.read_text().endswith("\nxx\n"))
         # Try with file without ending new line
-        with linguas_path.open('rb+') as fh:
+        with linguas_path.open("rb+") as fh:
             fh.seek(-1, os.SEEK_END)
             fh.truncate()
-        utils.insert_locale_in_linguas(linguas_path, 'xy')
-        self.assertTrue(linguas_path.read_text().endswith('\nxy\n'))
+        utils.insert_locale_in_linguas(linguas_path, "xy")
+        self.assertTrue(linguas_path.read_text().endswith("\nxy\n"))
         # Also works if file is initially empty
         os.truncate(linguas_path, 0)
-        utils.insert_locale_in_linguas(linguas_path, 'xz')
-        self.assertEqual(linguas_path.read_text(), 'xz\n')
+        utils.insert_locale_in_linguas(linguas_path, "xz")
+        self.assertEqual(linguas_path.read_text(), "xz\n")
 
     def test_exclude_messages_from_potfile(self):
-        pot_file = Path(__file__).parent / 'donottranslate.pot'
-        with tempfile.NamedTemporaryFile(suffix='.pot') as temppot:
+        pot_file = Path(__file__).parent / "donottranslate.pot"
+        with tempfile.NamedTemporaryFile(suffix=".pot") as temppot:
             shutil.copyfile(str(pot_file), temppot.name)
             utils.exclude_untrans_messages(temppot.name)
             stats = utils.po_file_stats(Path(temppot.name))
-        self.assertEqual(stats['untranslated'], 2)
+        self.assertEqual(stats["untranslated"], 2)
 
     @test_scratchdir
     def test_po_grep(self):
         # Take it.po, because desktop.in.h strings are not yet translated
-        it_path = settings.SCRATCHDIR / 'git' / 'gnome-hello' / 'po' / 'it.po'
+        it_path = settings.SCRATCHDIR / "git" / "gnome-hello" / "po" / "it.po"
         temp_file = tempfile.NamedTemporaryFile(delete=False)
         self.addCleanup(os.remove, temp_file.name)
-        utils.po_grep(it_path, temp_file.name, 'locations|desktop.in.h')
-        with open(temp_file.name, 'r') as fh:
+        utils.po_grep(it_path, temp_file.name, "locations|desktop.in.h")
+        with open(temp_file.name, "r") as fh:
             content = fh.read()
         self.assertGreater(len(content), 0)
-        self.assertNotIn('desktop.in.h', content)
+        self.assertNotIn("desktop.in.h", content)
 
     def test_po_quality(self):
         valid_path = Path(__file__).parent / "xml_valid.po"
-        result = utils.check_po_quality(str(valid_path), ['xmltags'])
-        self.assertEqual(result, '')
+        result = utils.check_po_quality(str(valid_path), ["xmltags"])
+        self.assertEqual(result, "")
 
         error_path = Path(__file__).parent / "xml_error.po"
-        result = utils.check_po_quality(str(error_path), ['xmltags'])
-        self.assertIn('xmltags: Different XML tags', result)
+        result = utils.check_po_quality(str(error_path), ["xmltags"])
+        self.assertIn("xmltags: Different XML tags", result)
 
-        result = utils.check_po_quality('unexisting/path.po', ['xmltags'])
+        result = utils.check_po_quality("unexisting/path.po", ["xmltags"])
         self.assertEqual(result, "The file “unexisting/path.po” does not exist")
 
 
 class OtherTests(TestCase):
     def test_release_list(self):
-        Release.objects.bulk_create([
-            Release(name='gnome-3-18', description='GNOME 3.18', status='official', weight=0),
-            Release(name='gnome-2-32', description='GNOME 2.32', status='official', weight=-10),
-        ])
-        response = self.client.get(reverse('releases'))
+        Release.objects.bulk_create(
+            [
+                Release(name="gnome-3-18", description="GNOME 3.18", status="official", weight=0),
+                Release(name="gnome-2-32", description="GNOME 2.32", status="official", weight=-10),
+            ]
+        )
+        response = self.client.get(reverse("releases"))
         contents = [
-            '<h1>GNOME Releases</h1>', '<h2>Older Releases</h2>',
-            '<a href="%s">GNOME 3.18</a>' % reverse('release', args=['gnome-3-18']),
+            "<h1>GNOME Releases</h1>",
+            "<h2>Older Releases</h2>",
+            '<a href="%s">GNOME 3.18</a>' % reverse("release", args=["gnome-3-18"]),
         ]
         for item in contents:
             self.assertContains(response, item)
         # Also test JSON output
-        response = self.client.get(reverse('releases', args=['json']))
-        content = json.loads(response.content.decode('utf-8'))
+        response = self.client.get(reverse("releases", args=["json"]))
+        content = json.loads(response.content.decode("utf-8"))
         self.assertEqual(content[0]["fields"]["name"], "gnome-3-18")
 
     def test_svn_checkout_cmd(self):
         from ..repos import SVNRepo
-        mod = Module.objects.create(
-            name='webkit', vcs_type='svn', vcs_root='https://svn.webkit.org/repository'
-        )
-        repo = SVNRepo(Branch(name='HEAD', module=mod, vcs_subpath='trunk'))
+
+        mod = Module.objects.create(name="webkit", vcs_type="svn", 
vcs_root="https://svn.webkit.org/repository";)
+        repo = SVNRepo(Branch(name="HEAD", module=mod, vcs_subpath="trunk"))
         with PatchShellCommand() as cmds:
             repo.init_checkout()
-        self.assertEqual(cmds, [
-            'svn co --non-interactive https://svn.webkit.org/repository/webkit/trunk '
-            '%s/svn/webkit.HEAD' % settings.SCRATCHDIR
-        ])
+        self.assertEqual(
+            cmds,
+            [
+                "svn co --non-interactive https://svn.webkit.org/repository/webkit/trunk "
+                "%s/svn/webkit.HEAD" % settings.SCRATCHDIR
+            ],
+        )
diff --git a/stats/tests/utils.py b/stats/tests/utils.py
index ccf48300..1b3b34e3 100644
--- a/stats/tests/utils.py
+++ b/stats/tests/utils.py
@@ -14,16 +14,17 @@ class PatchShellCommand:
     Mock common.utils.run_shell_commands and gather all passed commands.
     `only` is an optional list of commands to limit mocking to (empty -> all).
     """
+
     def __init__(self, only=None):
         self.only = only
 
     def __enter__(self):
         self.cmds = []
-        self.patcher1 = patch('stats.models.run_shell_command', side_effect=self.mocked_run_shell_command)
+        self.patcher1 = patch("stats.models.run_shell_command", side_effect=self.mocked_run_shell_command)
         self.patcher1.start()
-        self.patcher2 = patch('stats.utils.run_shell_command', side_effect=self.mocked_run_shell_command)
+        self.patcher2 = patch("stats.utils.run_shell_command", side_effect=self.mocked_run_shell_command)
         self.patcher2.start()
-        self.patcher3 = patch('stats.repos.run_shell_command', side_effect=self.mocked_run_shell_command)
+        self.patcher3 = patch("stats.repos.run_shell_command", side_effect=self.mocked_run_shell_command)
         self.patcher3.start()
         return self.cmds
 
@@ -33,10 +34,11 @@ class PatchShellCommand:
         if self.only is not None and not any(needle in cmd_str for needle in self.only):
             # Pass the command to the real utils.run_shell_command
             from common.utils import run_shell_command
+
             return run_shell_command(cmd, *args, **kwargs)
         else:
             # Pretend the command was successfull
-            return 0, '', ''
+            return 0, "", ""
 
     def __exit__(self, *args):
         self.patcher1.stop()
@@ -45,21 +47,23 @@ class PatchShellCommand:
 
 
 def test_scratchdir(test_func):
-    """ Decorator to temporarily use the scratchdir inside the test directory """
+    """Decorator to temporarily use the scratchdir inside the test directory"""
+
     @wraps(test_func)
     def decorator(self):
         old_scratchdir = settings.SCRATCHDIR
         old_potdir = settings.POTDIR
-        settings.SCRATCHDIR = Path(tempfile.mkdtemp()) / 'scratch'
+        settings.SCRATCHDIR = Path(tempfile.mkdtemp()) / "scratch"
         settings.POTDIR = settings.SCRATCHDIR / "POT"
         settings.POTDIR.mkdir(parents=True)
-        tar_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'gnome-hello.tar.gz')
+        tar_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "gnome-hello.tar.gz")
         with tarfile.open(tar_path) as gnome_hello_tar:
-            gnome_hello_tar.extractall(settings.SCRATCHDIR / 'git')
+            gnome_hello_tar.extractall(settings.SCRATCHDIR / "git")
         try:
             test_func(self)
         finally:
             shutil.rmtree(settings.SCRATCHDIR)
             settings.SCRATCHDIR = old_scratchdir
             settings.POTDIR = old_potdir
+
     return decorator
diff --git a/stats/utils.py b/stats/utils.py
index d7495330..ec6acfa4 100644
--- a/stats/utils.py
+++ b/stats/utils.py
@@ -11,12 +11,13 @@ from django.conf import settings
 from django.core.files.base import File
 from django.template.loader import get_template
 from django.utils.functional import cached_property
-from django.utils.translation import gettext as _, gettext_noop
-
+from django.utils.translation import gettext as _
+from django.utils.translation import gettext_noop
 from translate.storage.base import TranslationStore
-from translate.tools import pogrep, pocount
+from translate.tools import pocount, pogrep
 
 from common.utils import run_shell_command, send_mail
+
 from . import potdiff
 
 STATUS_OK = 0
@@ -27,8 +28,8 @@ CHANGED_ONLY_FORMATTING = 1
 CHANGED_WITH_ADDITIONS = 2
 CHANGED_NO_ADDITIONS = 3
 
-ITSTOOL_PATH = getattr(settings, 'ITSTOOL_PATH', '')
-ITS_DATA_DIR = settings.SCRATCHDIR / 'data' / 'its'
+ITSTOOL_PATH = getattr(settings, "ITSTOOL_PATH", "")
+ITS_DATA_DIR = settings.SCRATCHDIR / "data" / "its"
 
 # monkey-patch ttk (https://github.com/translate/translate/issues/2129)
 orig_addunit = TranslationStore.addunit
@@ -51,19 +52,17 @@ class UndetectableDocFormat(Exception):
 
 
 class DocFormat:
-    itstool_regex = re.compile("^msgid \"external ref=\'(?P<path>[^\']*)\' md5=\'(?P<hash>[^\']*)\'\"")
+    itstool_regex = re.compile("^msgid \"external ref='(?P<path>[^']*)' md5='(?P<hash>[^']*)'\"")
 
     def __init__(self, domain, branch):
         self.branch = branch
         self.vcs_path = branch.domain_path(domain)
         self.makefile = MakefileWrapper.find_file(branch, [self.vcs_path])
         if self.makefile is None:
-            raise UndetectableDocFormat(
-                gettext_noop("Unable to find a makefile for module %s") % branch.module.name
-            )
-        has_page_files = any(f.suffix == '.page' for f in self.list_C_files())
-        self.format = 'mallard' if has_page_files else 'docbook'
-        self.tool = 'itstool'
+            raise UndetectableDocFormat(gettext_noop("Unable to find a makefile for module %s") % 
branch.module.name)
+        has_page_files = any(f.suffix == ".page" for f in self.list_C_files())
+        self.format = "mallard" if has_page_files else "docbook"
+        self.tool = "itstool"
 
     def __repr__(self):
         return "%s format=%s, tool=%s>" % (self.__class__, self.format, self.tool)
@@ -73,7 +72,7 @@ class DocFormat:
         return isinstance(self.makefile, MesonfileWrapper)
 
     def list_C_files(self):
-        return [p.relative_to(self.vcs_path) for p in (self.vcs_path / 'C').iterdir() if p.is_file()]
+        return [p.relative_to(self.vcs_path) for p in (self.vcs_path / "C").iterdir() if p.is_file()]
 
     def source_files(self):
         """Return help source files, with path relative to help base dir."""
@@ -89,29 +88,25 @@ class DocFormat:
                     break
             if not sources:
                 # Last try: only one xml/docbook file in C/...
-                xml_files = [
-                    f for f in self.list_C_files() if f.suffix in ('.xml', '.docbook')
-                ]
+                xml_files = [f for f in self.list_C_files() if f.suffix in (".xml", ".docbook")]
                 if len(xml_files) == 1:
                     sources.append(xml_files[0].name)
                 else:
-                    raise Exception(
-                        gettext_noop("Unable to find doc source files for this module.")
-                    )
+                    raise Exception(gettext_noop("Unable to find doc source files for this module."))
 
         source_list = self.makefile.read_variable(*self.include_var)
         if not source_list:
-            suffix = ['.page'] if self.format == 'mallard' else ['.xml', '.docbook']
+            suffix = [".page"] if self.format == "mallard" else [".xml", ".docbook"]
             # Fallback to directory listing
             return [f for f in self.list_C_files() if f.suffix in suffix]
         if isinstance(source_list, str):
             sources += source_list.split()
         elif source_list:
             sources += source_list
-        return [Path('C', src) for src in sources if src not in ('', '$(NULL)')]
+        return [Path("C", src) for src in sources if src not in ("", "$(NULL)")]
 
     def command(self, potfile, files):
-        cmd = ['%sitstool' % ITSTOOL_PATH, '-o', str(potfile)]
+        cmd = ["%sitstool" % ITSTOOL_PATH, "-o", str(potfile)]
         cmd.extend([str(f) for f in files])
         return cmd
 
@@ -129,7 +124,7 @@ class DocFormat:
 
     @property
     def img_grep(self):
-        return "^msgid \"external ref="
+        return '^msgid "external ref='
 
     @property
     def bef_line(self):
@@ -142,7 +137,7 @@ class DocFormat:
 
 
 class MakefileWrapper:
-    default_makefiles = ['Makefile.am', 'meson.build', 'CMakeLists.txt']
+    default_makefiles = ["Makefile.am", "meson.build", "CMakeLists.txt"]
 
     @classmethod
     def find_file(cls, branch, vcs_paths, file_name=None):
@@ -152,9 +147,9 @@ class MakefileWrapper:
             for file_name in names:
                 file_path = path / file_name
                 if file_path.exists():
-                    if file_name == 'meson.build':
+                    if file_name == "meson.build":
                         return MesonfileWrapper(branch, file_path)
-                    elif file_name == 'CMakeLists.txt':
+                    elif file_name == "CMakeLists.txt":
                         return CMakefileWrapper(branch, file_path)
                     else:
                         return MakefileWrapper(branch, file_path)
@@ -166,7 +161,7 @@ class MakefileWrapper:
     @cached_property
     def content(self):
         try:
-            content = self.path.read_text(encoding='utf-8')
+            content = self.path.read_text(encoding="utf-8")
         except IOError:
             return None
         return content
@@ -178,7 +173,7 @@ class MakefileWrapper:
         found = None
         for line in self.content.splitlines():
             line = line.strip()
-            if line.startswith('#'):
+            if line.startswith("#"):
                 continue
             if non_terminated_content:
                 line = non_terminated_content + " " + line
@@ -202,19 +197,19 @@ class MakefileWrapper:
         # Try to expand '$(var)' variables
         if found:
             for element in found.split():
-                if element.startswith('$(') and element.endswith(')') and element != '$(NULL)':
+                if element.startswith("$(") and element.endswith(")") and element != "$(NULL)":
                     result = self.read_variable(element[2:-1])
                     if result:
                         found = found.replace(element, result)
-            if '$(top_srcdir)' in found:
-                found = found.replace('$(top_srcdir)', str(self.branch.co_path))
+            if "$(top_srcdir)" in found:
+                found = found.replace("$(top_srcdir)", str(self.branch.co_path))
         return found
 
 
 class MesonfileWrapper(MakefileWrapper):
-    i18n_gettext_kwargs = {'po_dir', 'data_dirs', 'type', 'languages', 'args', 'preset'}
-    gnome_yelp_kwargs = {'sources', 'media', 'symlink_media', 'languages'}
-    ignorable_kwargs = {'install_dir'}
+    i18n_gettext_kwargs = {"po_dir", "data_dirs", "type", "languages", "args", "preset"}
+    gnome_yelp_kwargs = {"sources", "media", "symlink_media", "languages"}
+    ignorable_kwargs = {"install_dir"}
     readable = True
 
     @cached_property
@@ -222,21 +217,14 @@ class MesonfileWrapper(MakefileWrapper):
         content = super().content
         # Here be dragons: Try to make meson content look like Python
         possible_kwargs = list(self.i18n_gettext_kwargs | self.gnome_yelp_kwargs | self.ignorable_kwargs)
-        content = re.sub(
-            r'(' + '|'.join(possible_kwargs) + ') ?:',
-            r'\1=',
-            content
-        )
+        content = re.sub(r"(" + "|".join(possible_kwargs) + ") ?:", r"\1=", content)
         # ignore if/endif sections
-        content = re.sub(r"^if .*endif$", '', content, flags=re.M | re.S)
-        content = content.replace(
-            'true', 'True'
-        ).replace(
-            'false', 'False'
-        ).replace(
-            "i18n = import('i18n')", ''
-        ).replace(
-            "gnome = import('gnome')", ''
+        content = re.sub(r"^if .*endif$", "", content, flags=re.M | re.S)
+        content = (
+            content.replace("true", "True")
+            .replace("false", "False")
+            .replace("i18n = import('i18n')", "")
+            .replace("gnome = import('gnome')", "")
         )
         return content
 
@@ -248,16 +236,16 @@ class MesonfileWrapper(MakefileWrapper):
 
         class VarCatcher:
             def yelp(self, *args, **kwargs):
-                catched['yelp.project_id'] = args[0]
-                for var_name in {'sources', 'languages'}:
+                catched["yelp.project_id"] = args[0]
+                for var_name in {"sources", "languages"}:
                     if var_name in kwargs:
-                        catched['yelp.%s' % var_name] = kwargs[var_name]
+                        catched["yelp.%s" % var_name] = kwargs[var_name]
 
             def gettext(self, *args, **kwargs):
-                catched['gettext.project_id'] = args[0]
+                catched["gettext.project_id"] = args[0]
                 for var_name in MesonfileWrapper.i18n_gettext_kwargs:
                     if var_name in kwargs:
-                        catched['gettext.%s' % var_name] = kwargs[var_name]
+                        catched["gettext.%s" % var_name] = kwargs[var_name]
 
         catcher = VarCatcher()
         this_instance = self
@@ -270,10 +258,13 @@ class MesonfileWrapper(MakefileWrapper):
                 return str(this_instance.branch.co_path)
 
         meson_locals = {
-            'gnome': catcher, 'i18n': catcher,
-            'GNOME': catcher, 'I18N': catcher,
-            'install_data': MagicMock(), 'meson': MesonMock(),
-            'join_paths': lambda *args: os.sep.join(args),
+            "gnome": catcher,
+            "i18n": catcher,
+            "GNOME": catcher,
+            "I18N": catcher,
+            "install_data": MagicMock(),
+            "meson": MesonMock(),
+            "join_paths": lambda *args: os.sep.join(args),
         }
         while True:
             try:
@@ -321,13 +312,13 @@ class CMakefileWrapper(MakefileWrapper):
             if non_terminated_content:
                 line = non_terminated_content + " " + line
             # Test if line is non terminated
-            if line.count('(') > line.count(')'):
+            if line.count("(") > line.count(")"):
                 non_terminated_content = line
             else:
                 non_terminated_content = ""
                 # Search for variable
                 for var in variables:
-                    match = re.search(r'set\s*\(%s\s*([^\)]*)\)' % var, line)
+                    match = re.search(r"set\s*\(%s\s*([^\)]*)\)" % var, line)
                     if match:
                         found = match.group(1).strip()
                         break
@@ -337,7 +328,7 @@ class CMakefileWrapper(MakefileWrapper):
 
 
 def sort_object_list(lst, sort_meth):
-    """ Sort an object list with sort_meth (which should return a translated string) """
+    """Sort an object list with sort_meth (which should return a translated string)"""
     templist = [(getattr(obj_, sort_meth)().lower(), obj_) for obj_ in lst]
     templist.sort()
     return [obj_ for (key1, obj_) in templist]
@@ -345,7 +336,7 @@ def sort_object_list(lst, sort_meth):
 
 def multiple_replace(dct, text):
     regex = re.compile("(%s)" % "|".join(map(re.escape, dct.keys())))
-    return regex.sub(lambda mo: dct[mo.string[mo.start():mo.end()]], text)
+    return regex.sub(lambda mo: dct[mo.string[mo.start() : mo.end()]], text)
 
 
 def stripHTML(string):
@@ -360,8 +351,8 @@ def ellipsize(val, length):
 
 
 def check_program_presence(prog_name):
-    """ Test if prog_name is an available command on the system """
-    status, output, err = run_shell_command(['which', prog_name])
+    """Test if prog_name is an available command on the system"""
+    status, output, err = run_shell_command(["which", prog_name])
     return status == 0
 
 
@@ -395,11 +386,11 @@ def po_grep(in_file, out_file, filter_):
 
 def check_potfiles(po_path):
     """Check if there were any problems regenerating a POT file (intltool-update -m).
-       Return a list of errors """
+    Return a list of errors"""
     errors = []
 
-    run_shell_command(['rm', '-f', 'missing', 'notexist'], cwd=po_path)
-    status, output, errs = run_shell_command(['intltool-update', '-m'], cwd=po_path)
+    run_shell_command(["rm", "-f", "missing", "notexist"], cwd=po_path)
+    status, output, errs = run_shell_command(["intltool-update", "-m"], cwd=po_path)
 
     if status != STATUS_OK:
         errors.append(("error", gettext_noop("Errors while running “intltool-update -m” check.")))
@@ -410,10 +401,9 @@ def check_potfiles(po_path):
             errors.append(
                 (
                     "warn",
-                    gettext_noop(
-                         "There are some missing files from POTFILES.in: %s"
-                     ) % ("<ul><li>" + "</li>\n<li>".join(fh.readlines()) + "</li>\n</ul>")
-                 )
+                    gettext_noop("There are some missing files from POTFILES.in: %s")
+                    % ("<ul><li>" + "</li>\n<li>".join(fh.readlines()) + "</li>\n</ul>"),
+                )
             )
 
     notexist = po_path / "notexist"
@@ -425,14 +415,15 @@ def check_potfiles(po_path):
                     gettext_noop(
                         "Following files are referenced in either POTFILES.in or POTFILES.skip, "
                         "yet they don’t exist: %s"
-                    ) % ("<ul><li>" + "</li>\n<li>".join(fh.readlines()) + "</li>\n</ul>")
+                    )
+                    % ("<ul><li>" + "</li>\n<li>".join(fh.readlines()) + "</li>\n</ul>"),
                 )
             )
     return errors
 
 
 def generate_doc_pot_file(branch, domain):
-    """ Return the pot file for a document-type domain, and the error if any """
+    """Return the pot file for a document-type domain, and the error if any"""
     try:
         doc_format = DocFormat(domain, branch)
         files = doc_format.source_files()
@@ -445,13 +436,17 @@ def generate_doc_pot_file(branch, domain):
     status, output, errs = run_shell_command(command, cwd=doc_format.vcs_path)
 
     if status != STATUS_OK:
-        return "", [(
-            "error",
-            gettext_noop("Error regenerating POT file for document 
%(file)s:\n<pre>%(cmd)s\n%(output)s</pre>") % {
-                'file': potbase,
-                'cmd': " ".join([c.replace(str(settings.SCRATCHDIR), "&lt;scratchdir&gt;") for c in 
command]),
-                'output': errs
-            })]
+        return "", [
+            (
+                "error",
+                gettext_noop("Error regenerating POT file for document 
%(file)s:\n<pre>%(cmd)s\n%(output)s</pre>")
+                % {
+                    "file": potbase,
+                    "cmd": " ".join([c.replace(str(settings.SCRATCHDIR), "&lt;scratchdir&gt;") for c in 
command]),
+                    "output": errs,
+                },
+            )
+        ]
 
     if not potfile.exists():
         return "", []
@@ -482,38 +477,28 @@ def check_po_conformity(pofile):
         input_data = pofile.read()
         if isinstance(input_data, bytes):
             try:
-                input_data = input_data.decode('utf-8')
+                input_data = input_data.decode("utf-8")
             except UnicodeDecodeError:
-                return [(
-                    "warn",
-                    gettext_noop("PO file “%s” is not UTF-8 encoded.") % pofile.name
-                )]
+                return [("warn", gettext_noop("PO file “%s” is not UTF-8 encoded.") % pofile.name)]
         input_file = "-"
     else:
         input_data = None
         input_file = str(pofile)
 
-    command = ['msgfmt', '-cv', '-o', '/dev/null', input_file]
+    command = ["msgfmt", "-cv", "-o", "/dev/null", input_file]
     status, _, _ = run_shell_command(command, env=C_ENV, input_data=input_data)
     if status != STATUS_OK:
-        errors.append((
-            "error",
-            gettext_noop("PO file “%s” doesn’t pass msgfmt check.") % pofile.name
-        ))
+        errors.append(("error", gettext_noop("PO file “%s” doesn’t pass msgfmt check.") % pofile.name))
 
-    if input_file != '-' and os.access(str(pofile), os.X_OK):
+    if input_file != "-" and os.access(str(pofile), os.X_OK):
         errors.append(("warn", gettext_noop("This PO file has an executable bit set.")))
 
     # Check if PO file is in UTF-8
     if input_file != "-":
-        command = "msgconv -t UTF-8 \"%s\" | diff -i -I '^#~' -u \"%s\" - >/dev/null" % (
-            pofile, pofile)
+        command = 'msgconv -t UTF-8 "%s" | diff -i -I \'^#~\' -u "%s" - >/dev/null' % (pofile, pofile)
         status, _, _ = run_shell_command(command, env=C_ENV)
         if status != STATUS_OK:
-            errors.append((
-                "warn",
-                gettext_noop("PO file “%s” is not UTF-8 encoded.") % pofile.name
-            ))
+            errors.append(("warn", gettext_noop("PO file “%s” is not UTF-8 encoded.") % pofile.name))
     return errors
 
 
@@ -524,11 +509,7 @@ def check_po_quality(pofile, filters):
     """
     if not os.path.exists(pofile):
         return _("The file “%s” does not exist") % pofile
-    status, out, errs = run_shell_command(
-        ['pofilter', '--progress=none', '--gnome', '-t'] +
-        list(filters) +
-        [pofile]
-    )
+    status, out, errs = run_shell_command(["pofilter", "--progress=none", "--gnome", "-t"] + list(filters) + 
[pofile])
     if status == STATUS_OK:
         return out
     else:
@@ -538,20 +519,17 @@ def check_po_quality(pofile, filters):
 def po_file_stats(pofile):
     """Compute pofile translation statistics."""
     res = {
-        'translated': 0,
-        'fuzzy': 0,
-        'untranslated': 0,
-        'translated_words': 0,
-        'fuzzy_words': 0,
-        'untranslated_words': 0,
-        'errors': [],
+        "translated": 0,
+        "fuzzy": 0,
+        "untranslated": 0,
+        "translated_words": 0,
+        "fuzzy_words": 0,
+        "untranslated_words": 0,
+        "errors": [],
     }
 
     if not pofile.exists():
-        res['errors'].append((
-            "error",
-            gettext_noop("PO file “%s” does not exist or cannot be read.") % pofile.name
-        ))
+        res["errors"].append(("error", gettext_noop("PO file “%s” does not exist or cannot be read.") % 
pofile.name))
         return res
 
     try:
@@ -560,44 +538,41 @@ def po_file_stats(pofile):
         # From translate-toolkit 3.3
         status = pocount.calcstats(str(pofile))
     if not status:
-        res['errors'].append((
-            "error",
-            gettext_noop("Can’t get statistics for POT file “%s”.") % pofile.name
-        ))
+        res["errors"].append(("error", gettext_noop("Can’t get statistics for POT file “%s”.") % 
pofile.name))
     else:
-        res['fuzzy'] = status['fuzzy']
-        res['translated'] = status['translated']
-        res['untranslated'] = status['untranslated']
-        res['fuzzy_words'] = status['fuzzysourcewords']
-        res['translated_words'] = status['translatedsourcewords']
-        res['untranslated_words'] = status['untranslatedsourcewords']
+        res["fuzzy"] = status["fuzzy"]
+        res["translated"] = status["translated"]
+        res["untranslated"] = status["untranslated"]
+        res["fuzzy_words"] = status["fuzzysourcewords"]
+        res["translated_words"] = status["translatedsourcewords"]
+        res["untranslated_words"] = status["untranslatedsourcewords"]
 
     return res
 
 
 def read_linguas_file(full_path):
-    """ Read a LINGUAS file (each language code on a line by itself) """
+    """Read a LINGUAS file (each language code on a line by itself)"""
     langs = []
     with full_path.open("r") as fh:
-        [langs.extend(line.split()) for line in fh if line[:1] != '#']
+        [langs.extend(line.split()) for line in fh if line[:1] != "#"]
     return {
-        'langs': langs,
-        'error': gettext_noop("Entry for this language is not present in LINGUAS file."),
+        "langs": langs,
+        "error": gettext_noop("Entry for this language is not present in LINGUAS file."),
     }
 
 
 def insert_locale_in_linguas(linguas_path, locale):
     temp_linguas = linguas_path.parent / (linguas_path.name + "~")
-    with linguas_path.open('r') as fin, temp_linguas.open('w') as fout:
+    with linguas_path.open("r") as fin, temp_linguas.open("w") as fout:
         lang_written = False
-        line = '\n'
+        line = "\n"
         for line in fin:
             if not lang_written and line[0] != "#" and line[:5] > locale[:5]:
                 fout.write(locale + "\n")
                 lang_written = True
             fout.write(line)
         if not lang_written:
-            fout.write(('\n' if not line.endswith('\n') else '') + locale + "\n")
+            fout.write(("\n" if not line.endswith("\n") else "") + locale + "\n")
     temp_linguas.replace(linguas_path)
 
 
@@ -614,22 +589,25 @@ def get_ui_linguas(branch, subdir):
     # AS_ALL_LINGUAS is a macro that takes all po files by default
     status, output, errs = run_shell_command("grep -qs AS_ALL_LINGUAS %s%sconfigure.*" % (branch.co_path, 
os.sep))
     if status == 0:
-        return {'langs': None,
-                'error': gettext_noop("No need to edit LINGUAS file or variable for this module")}
+        return {"langs": None, "error": gettext_noop("No need to edit LINGUAS file or variable for this 
module")}
 
     configureac = branch.co_path / "configure.ac"
     configurein = branch.co_path / "configure.in"
     for configure in [configureac, configurein]:
-        found = MakefileWrapper(branch, configure).read_variable('ALL_LINGUAS')
+        found = MakefileWrapper(branch, configure).read_variable("ALL_LINGUAS")
         if found is not None:
-            return {'langs': found.split(),
-                    'error': gettext_noop("Entry for this language is not present in ALL_LINGUAS in 
configure file.")}
-    return {'langs': None,
-            'error': gettext_noop("Don’t know where to look for the LINGUAS variable, ask the module 
maintainer.")}
+            return {
+                "langs": found.split(),
+                "error": gettext_noop("Entry for this language is not present in ALL_LINGUAS in configure 
file."),
+            }
+    return {
+        "langs": None,
+        "error": gettext_noop("Don’t know where to look for the LINGUAS variable, ask the module 
maintainer."),
+    }
 
 
 def get_doc_linguas(branch, subdir):
-    """Get language list in one Makefile.am (either path) """
+    """Get language list in one Makefile.am (either path)"""
     linguas = None
     po_path = branch.co_path / subdir
 
@@ -642,11 +620,14 @@ def get_doc_linguas(branch, subdir):
     if linguas_file:
         linguas = linguas_file.read_variable("DOC_LINGUAS", "HELP_LINGUAS", "gettext.languages")
     if linguas is None:
-        return {'langs': None,
-                'error': gettext_noop(
-                    "Don’t know where to look for the DOC_LINGUAS variable, ask the module maintainer.")}
-    return {'langs': linguas.split() if isinstance(linguas, str) else linguas,
-            'error': gettext_noop("DOC_LINGUAS list doesn’t include this language.")}
+        return {
+            "langs": None,
+            "error": gettext_noop("Don’t know where to look for the DOC_LINGUAS variable, ask the module 
maintainer."),
+        }
+    return {
+        "langs": linguas.split() if isinstance(linguas, str) else linguas,
+        "error": gettext_noop("DOC_LINGUAS list doesn’t include this language."),
+    }
 
 
 def collect_its_data():
@@ -658,7 +639,7 @@ def collect_its_data():
 
     if not ITS_DATA_DIR.exists():
         ITS_DATA_DIR.mkdir(parents=True)
-    data_to_collect = getattr(settings, 'GETTEXT_ITS_DATA', {})
+    data_to_collect = getattr(settings, "GETTEXT_ITS_DATA", {})
     for module_name, files in data_to_collect.items():
         mod = Module.objects.get(name=module_name)
         branch = mod.get_head_branch()
@@ -671,33 +652,35 @@ def collect_its_data():
 
 
 def get_fig_stats(pofile, doc_format):
-    """ Extract image strings from pofile and return a list of figures dict:
-        [{'path':, 'hash':, 'fuzzy':, 'translated':}, ...] """
+    """Extract image strings from pofile and return a list of figures dict:
+    [{'path':, 'hash':, 'fuzzy':, 'translated':}, ...]"""
     if not isinstance(doc_format, DocFormat):
         return []
     # Extract image strings: beforeline/msgid/msgstr/grep auto output a fourth line
     before_lines = doc_format.bef_line
     command = "msgcat --no-wrap %(pofile)s| grep -A 1 -B %(before)s '%(grep)s'" % {
-        'pofile': pofile, 'grep': doc_format.img_grep, 'before': before_lines,
+        "pofile": pofile,
+        "grep": doc_format.img_grep,
+        "before": before_lines,
     }
     (status, output, errs) = run_shell_command(command)
-    if status != STATUS_OK or output == '':
+    if status != STATUS_OK or output == "":
         # FIXME: something should be logged here
         return []
-    lines = output.split('\n')
+    lines = output.split("\n")
     while lines[0][0] != "#":
         lines = lines[1:]  # skip warning messages at the top of the output
 
     figures = []
     for i, line in islice(enumerate(lines), 0, None, 3 + before_lines):
         # TODO: add image size
-        fig = {"path": '', "hash": ''}
+        fig = {"path": "", "hash": ""}
         m = doc_format.img_regex.match(lines[i + before_lines])
         if m:
-            fig["path"] = m.group('path')
-            fig["hash"] = m.group('hash')
-        fig["fuzzy"] = (line == '#, fuzzy' or line[:8] == '#| msgid')
-        fig["translated"] = len(lines[i + before_lines + 1]) > 9 and not fig['fuzzy']
+            fig["path"] = m.group("path")
+            fig["hash"] = m.group("hash")
+        fig["fuzzy"] = line == "#, fuzzy" or line[:8] == "#| msgid"
+        fig["translated"] = len(lines[i + before_lines + 1]) > 9 and not fig["fuzzy"]
         figures.append(fig)
     return figures
 
@@ -705,10 +688,10 @@ def get_fig_stats(pofile, doc_format):
 def check_identical_figures(fig_stats, base_path, lang):
     errors = []
     for fig in fig_stats:
-        trans_path = base_path / lang / fig['path']
+        trans_path = base_path / lang / fig["path"]
         if trans_path.exists():
             trans_hash = compute_md5(trans_path)
-            if fig['hash'] == trans_hash:
+            if fig["hash"] == trans_hash:
                 errors.append(
                     ("warn-ext", "Figures should not be copied when identical to original (%s)." % 
trans_path)
                 )
@@ -716,7 +699,7 @@ def check_identical_figures(fig_stats, base_path, lang):
 
 
 def add_custom_header(po_path, header, value):
-    """ Add a custom po file header """
+    """Add a custom po file header"""
     grep_cmd = """grep "%s" %s""" % (header, po_path)
     status = 1
     last_headers = ["Content-Transfer-Encoding", "Plural-Forms"]
@@ -724,23 +707,23 @@ def add_custom_header(po_path, header, value):
         (status, output, errs) = run_shell_command(grep_cmd)
         if status != 0:
             # Try to add header
-            cmd = '''sed -i '/^\"%s/ a\\"%s: %s\\\\n"' %s''' % (last_headers.pop(), header, value, po_path)
+            cmd = """sed -i '/^\"%s/ a\\"%s: %s\\\\n"' %s""" % (last_headers.pop(), header, value, po_path)
             (stat, out, err) = run_shell_command(cmd)
     if status == 0 and not "%s: %s" % (header, value) in output:
         # Set header value
-        cmd = '''sed -i '/^\"%s/ c\\"%s: %s\\\\n"' %s''' % (header, header, value, po_path)
+        cmd = """sed -i '/^\"%s/ c\\"%s: %s\\\\n"' %s""" % (header, header, value, po_path)
         (stat, out, err) = run_shell_command(cmd)
 
 
 def exclude_untrans_messages(potfile):
     """Exclude translatable strings matching some translator comments."""
-    exclude_message = 'translators: do not translate or transliterate this text'
+    exclude_message = "translators: do not translate or transliterate this text"
     # Grep first the file to see if the message is in the file.
-    status, _, _ = run_shell_command(['grep', '-i', exclude_message, potfile])
+    status, _, _ = run_shell_command(["grep", "-i", exclude_message, potfile])
     if status != STATUS_OK:
         return
 
-    with open(str(potfile), encoding='utf-8', mode='r+') as fh:
+    with open(str(potfile), encoding="utf-8", mode="r+") as fh:
         lines = fh.readlines()
         fh.seek(0)
         skip_unit = False
@@ -749,19 +732,19 @@ def exclude_untrans_messages(potfile):
                 fh.write(line)
             else:
                 # A blank line is resetting skip_unit
-                skip_unit = line != '\n'
+                skip_unit = line != "\n"
         fh.truncate()
 
 
 def is_po_reduced(file_path):
-    status, output, errs = run_shell_command(['grep', 'X-DamnedLies-Scope: partial', file_path])
-    return (status == 0)
+    status, output, errs = run_shell_command(["grep", "X-DamnedLies-Scope: partial", file_path])
+    return status == 0
 
 
 def compute_md5(full_path):
     m = hashlib.md5()
-    block_size = 2 ** 13
-    with full_path.open('rb') as fh:
+    block_size = 2**13
+    with full_path.open("rb") as fh:
         while True:
             data = fh.read(block_size)
             if not data:
@@ -772,22 +755,26 @@ def compute_md5(full_path):
 
 def notify_list(branch, diff):
     """Send notification about string changes described in diff."""
-    template = get_template('freeze-notification.txt')
-    text = template.render({
-        'module': '%s.%s' % (branch.module.name, branch.name),
-        'ourweb': settings.SITE_DOMAIN,
-        'potdiff': "\n    ".join(diff),
-        'commit_log': branch.get_vcs_web_log_url(),
-    })
-    send_mail("String additions to '%s.%s'" % (branch.module.name, branch.name),
-              text,
-              from_email="GNOME Status Pages <%s>" % settings.DEFAULT_FROM_EMAIL,
-              to=settings.NOTIFICATIONS_TO,
-              headers={settings.EMAIL_HEADER_NAME: "stringchange"})
+    template = get_template("freeze-notification.txt")
+    text = template.render(
+        {
+            "module": "%s.%s" % (branch.module.name, branch.name),
+            "ourweb": settings.SITE_DOMAIN,
+            "potdiff": "\n    ".join(diff),
+            "commit_log": branch.get_vcs_web_log_url(),
+        }
+    )
+    send_mail(
+        "String additions to '%s.%s'" % (branch.module.name, branch.name),
+        text,
+        from_email="GNOME Status Pages <%s>" % settings.DEFAULT_FROM_EMAIL,
+        to=settings.NOTIFICATIONS_TO,
+        headers={settings.EMAIL_HEADER_NAME: "stringchange"},
+    )
 
 
 def url_join(base, *args):
-    """ Create an url in joining base with arguments. A lot nicer than urlparse.urljoin! """
+    """Create an url in joining base with arguments. A lot nicer than urlparse.urljoin!"""
     url = base
     for arg in args:
         if url[-1] != "/":
diff --git a/stats/views.py b/stats/views.py
index 97a0f3dd..0e77ba17 100644
--- a/stats/views.py
+++ b/stats/views.py
@@ -1,35 +1,47 @@
-from datetime import date
 import os
+from datetime import date
 
 from django.conf import settings
-from django.contrib.auth.decorators import login_required
 from django.contrib import messages
+from django.contrib.auth.decorators import login_required
 from django.core import serializers
-from django.http import HttpResponse, HttpResponseForbidden, HttpResponseRedirect, Http404
-from django.shortcuts import render, get_object_or_404
+from django.http import (
+    Http404,
+    HttpResponse,
+    HttpResponseForbidden,
+    HttpResponseRedirect,
+)
+from django.shortcuts import get_object_or_404, render
 from django.urls import reverse
 from django.utils.translation import gettext as _
 
 from common.utils import MIME_TYPES, get_user_locale, run_shell_command
+from languages.models import Language
+from people.models import Person
+from stats.forms import ModuleBranchForm
 from stats.models import (
-    Branch, Category, Domain, FakeLangStatistics, Module, ModuleLock, Release, Statistics
+    Branch,
+    Category,
+    Domain,
+    FakeLangStatistics,
+    Module,
+    ModuleLock,
+    Release,
+    Statistics,
 )
-from stats.forms import ModuleBranchForm
 from stats.utils import sort_object_list
-from languages.models import Language
-from people.models import Person
 
 
-def modules(request, format='html'):
+def modules(request, format="html"):
     all_modules = Module.objects.all()
-    if format in ('json', 'xml'):
+    if format in ("json", "xml"):
         data = serializers.serialize(format, all_modules)
         return HttpResponse(data, content_type=MIME_TYPES[format])
     context = {
-        'pageSection': "module",
-        'modules': sort_object_list(all_modules, 'get_description'),
+        "pageSection": "module",
+        "modules": sort_object_list(all_modules, "get_description"),
     }
-    return render(request, 'module_list.html', context)
+    return render(request, "module_list.html", context)
 
 
 def module(request, module_name):
@@ -44,19 +56,19 @@ def module(request, module_name):
             branch.get_doc_stats(mandatory_langs=langs)
 
     context = {
-        'pageSection': "module",
-        'module': mod,
-        'branches': branches,
-        'non_standard_repo_msg': _(settings.VCS_HOME_WARNING),
-        'can_edit_branches': mod.can_edit_branches(request.user),
-        'can_refresh': can_refresh_branch(request.user),
-        'user_language': get_user_locale(request)
+        "pageSection": "module",
+        "module": mod,
+        "branches": branches,
+        "non_standard_repo_msg": _(settings.VCS_HOME_WARNING),
+        "can_edit_branches": mod.can_edit_branches(request.user),
+        "can_refresh": can_refresh_branch(request.user),
+        "user_language": get_user_locale(request),
     }
-    return render(request, 'module_detail.html', context)
+    return render(request, "module_detail.html", context)
 
 
 def module_branch(request, module_name, branch_name):
-    """ This view is used to dynamically load a specific branch stats (jquery.load) """
+    """This view is used to dynamically load a specific branch stats (jquery.load)"""
     mod = get_object_or_404(Module, name=module_name)
     branch = get_object_or_404(mod.branch_set, name=branch_name)
     if request.user.is_authenticated:
@@ -67,10 +79,10 @@ def module_branch(request, module_name, branch_name):
         branch.get_doc_stats(mandatory_langs=langs)
 
     context = {
-        'module': mod,
-        'branch': branch,
+        "module": mod,
+        "branch": branch,
     }
-    return render(request, 'branch_detail.html', context)
+    return render(request, "branch_detail.html", context)
 
 
 @login_required
@@ -80,13 +92,13 @@ def module_edit_branches(request, module_name):
         messages.error(request, "Sorry, you're not allowed to edit this module's branches")
         return HttpResponseRedirect(mod.get_absolute_url())
 
-    if request.method == 'POST':
+    if request.method == "POST":
         form = ModuleBranchForm(mod, request.POST)
         if form.is_valid():
             updated = False
             # Modified or deleted release or category for branch
             for key, field in form.fields.items():
-                if not getattr(field, 'is_branch', False):
+                if not getattr(field, "is_branch", False):
                     continue
                 if form.fields[key].initial and not form.cleaned_data[key]:
                     # Delete category
@@ -94,26 +106,26 @@ def module_edit_branches(request, module_name):
                     updated = True
                     continue
                 release_has_changed = key in form.changed_data
-                category_has_changed = (key + '_cat') in form.changed_data
+                category_has_changed = (key + "_cat") in form.changed_data
                 if form.cleaned_data[key] and (release_has_changed or category_has_changed):
                     if field.initial:
                         cat = Category.objects.get(pk=key)
                         if release_has_changed:
                             cat.release = form.cleaned_data[key]
-                        cat.name = form.cleaned_data[key + '_cat']
+                        cat.name = form.cleaned_data[key + "_cat"]
                     else:
                         rel = Release.objects.get(pk=form.cleaned_data[key].pk)
                         branch = Branch.objects.get(module=mod, name=key)
-                        cat = Category(release=rel, branch=branch, name=form.cleaned_data[key + '_cat'])
+                        cat = Category(release=rel, branch=branch, name=form.cleaned_data[key + "_cat"])
                     cat.save()
                     updated = True
             # New branch (or new category)
-            if form.cleaned_data['new_branch']:
-                if 'new_category' in form.cleaned_data:
-                    form.cleaned_data['new_category'].save()
+            if form.cleaned_data["new_branch"]:
+                if "new_category" in form.cleaned_data:
+                    form.cleaned_data["new_category"].save()
                     updated = True
                 else:
-                    branch_name = form.cleaned_data['new_branch']
+                    branch_name = form.cleaned_data["new_branch"]
                     try:
                         branch = Branch.objects.get(module=mod, name=branch_name)
                     except Branch.DoesNotExist:
@@ -136,10 +148,10 @@ def module_edit_branches(request, module_name):
     else:
         form = ModuleBranchForm(mod)
     context = {
-        'module': mod,
-        'form': form,
+        "module": mod,
+        "form": form,
     }
-    return render(request, 'module_edit_branches.html', context)
+    return render(request, "module_edit_branches.html", context)
 
 
 def branch_refresh(request, branch_id):
@@ -148,61 +160,61 @@ def branch_refresh(request, branch_id):
 
     branch = get_object_or_404(Branch, pk=branch_id)
     if ModuleLock(branch.module).is_locked:
-        messages.info(request, "A statistics update is already running for branch %s of module %s" % (
-            branch.name, branch.module.name))
+        messages.info(
+            request,
+            "A statistics update is already running for branch %s of module %s" % (branch.name, 
branch.module.name),
+        )
     else:
         try:
             branch.update_stats(force=True)
         except Exception as exc:
-            messages.error(request, "An error occurred while updating statistics for branch %s of module 
%s:<br>%s" % (
-                branch.name, branch.module.name, exc))
+            messages.error(
+                request,
+                "An error occurred while updating statistics for branch %s of module %s:<br>%s"
+                % (branch.name, branch.module.name, exc),
+            )
         else:
-            messages.success(request, "Statistics successfully updated for branch %s of module %s" % (
-                branch.name, branch.module.name))
-    return HttpResponseRedirect(reverse('module', args=[branch.module.name]))
+            messages.success(
+                request,
+                "Statistics successfully updated for branch %s of module %s" % (branch.name, 
branch.module.name),
+            )
+    return HttpResponseRedirect(reverse("module", args=[branch.module.name]))
 
 
 def docimages(request, module_name, potbase, branch_name, langcode):
     mod = get_object_or_404(Module, name=module_name)
     try:
         stat = Statistics.objects.get(
-            branch__module=mod.id,
-            branch__name=branch_name,
-            domain__name=potbase,
-            language__locale=langcode
+            branch__module=mod.id, branch__name=branch_name, domain__name=potbase, language__locale=langcode
         )
     except Statistics.DoesNotExist:
         pot_stat = get_object_or_404(
-            Statistics,
-            branch__module=mod.id,
-            branch__name=branch_name,
-            domain__name=potbase,
-            language__isnull=True
+            Statistics, branch__module=mod.id, branch__name=branch_name, domain__name=potbase, 
language__isnull=True
         )
         lang = get_object_or_404(Language, locale=langcode)
         stat = FakeLangStatistics(pot_stat, lang)
     context = {
-        'pageSection': "module",
-        'module': mod,
-        'stat': stat,
-        'locale': stat.language.locale,
-        'figstats': stat.fig_stats(),
+        "pageSection": "module",
+        "module": mod,
+        "stat": stat,
+        "locale": stat.language.locale,
+        "figstats": stat.fig_stats(),
     }
-    return render(request, 'module_images.html', context)
+    return render(request, "module_images.html", context)
 
 
 def dynamic_po(request, module_name, branch_name, domain_name, filename):
-    """ Generates a dynamic po file from the POT file of a branch """
+    """Generates a dynamic po file from the POT file of a branch"""
     try:
         locale, ext = filename.split(".")
-        if locale.endswith('-reduced'):
+        if locale.endswith("-reduced"):
             locale, reduced = locale[:-8], True
         else:
             reduced = False
     except Exception:
         raise Http404
 
-    language = get_object_or_404(Language.objects.select_related('team'), locale=locale)
+    language = get_object_or_404(Language.objects.select_related("team"), locale=locale)
     branch = get_object_or_404(Branch, module__name=module_name, name=branch_name)
     try:
         domain = branch.get_domains()[domain_name]
@@ -217,16 +229,16 @@ def dynamic_po(request, module_name, branch_name, domain_name, filename):
     dyn_content = """# %(lang)s translation for %(pack)s.
 # Copyright (C) %(year)s %(pack)s's COPYRIGHT HOLDER
 # This file is distributed under the same license as the %(pack)s package.\n""" % {
-        'lang': language.name,
-        'pack': module_name,
-        'year': date.today().year
+        "lang": language.name,
+        "pack": module_name,
+        "year": date.today().year,
     }
     if request.user.is_authenticated:
         person = Person.get_by_user(request.user)
         dyn_content += "# %(name)s <%(email)s>, %(year)s.\n#\n" % {
-            'name': person.name,
-            'email': person.email,
-            'year': date.today().year,
+            "name": person.name,
+            "email": person.email,
+            "year": date.today().year,
         }
     else:
         dyn_content += "# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n#\n"
@@ -235,66 +247,60 @@ def dynamic_po(request, module_name, branch_name, domain_name, filename):
     try:
         status, output, err = run_shell_command(command, raise_on_error=True)
     except OSError:
-        return HttpResponse(
-            f"Unable to produce a new .po file for language {locale}, msginit command failed."
-        )
+        return HttpResponse(f"Unable to produce a new .po file for language {locale}, msginit command 
failed.")
     lines = output.split("\n")
     skip_next_line = False
     for i, line in enumerate(lines):
-        if skip_next_line or (line and line[0] == '#'):
+        if skip_next_line or (line and line[0] == "#"):
             # Skip first lines of the file
             skip_next_line = False
             continue
         # Transformations
         new_line = {
-            '"Project-Id-': "\"Project-Id-Version: %s %s\\n\"" % (module_name, branch_name),
-            '"Last-Transl': "\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"",
-            '"Language-Te': "\"Language-Team: %s <%s>\\n\"" % (
-                language.name, language.team and language.team.mailing_list or "%s li org" % locale),
-            '"Content-Typ': "\"Content-Type: text/plain; charset=UTF-8\\n\"",
-            '"Plural-Form': "\"Plural-Forms: %s;\\n\"" % (language.plurals or "nplurals=INTEGER; 
plural=EXPRESSION"),
+            '"Project-Id-': '"Project-Id-Version: %s %s\\n"' % (module_name, branch_name),
+            '"Last-Transl': '"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"',
+            '"Language-Te': '"Language-Team: %s <%s>\\n"'
+            % (language.name, language.team and language.team.mailing_list or "%s li org" % locale),
+            '"Content-Typ': '"Content-Type: text/plain; charset=UTF-8\\n"',
+            '"Plural-Form': '"Plural-Forms: %s;\\n"' % (language.plurals or "nplurals=INTEGER; 
plural=EXPRESSION"),
         }.get(line[:12], line)
-        if line != new_line and line[-3:] != "\\n\"":
+        if line != new_line and line[-3:] != '\\n"':
             # Skip the wrapped part of the replaced line
             skip_next_line = True
         dyn_content += new_line + "\n"
         if line == "":
             # Output the remaining part of the file untouched
-            dyn_content += "\n".join(lines[i + 1:])
+            dyn_content += "\n".join(lines[i + 1 :])
             break
-    response = HttpResponse(dyn_content, 'text/plain')
-    response['Content-Disposition'] = 'inline; filename=%s' % (
-        ".".join([domain.potbase(), branch.name_escaped, filename]))
+    response = HttpResponse(dyn_content, "text/plain")
+    response["Content-Disposition"] = "inline; filename=%s" % (
+        ".".join([domain.potbase(), branch.name_escaped, filename])
+    )
     return response
 
 
-def releases(request, format='html'):
-    active_releases = Release.objects.filter(weight__gte=0).order_by('status', '-weight', '-name')
-    old_releases = Release.objects.filter(weight__lt=0).order_by('status', '-weight', '-name')
-    if format in ('json', 'xml'):
+def releases(request, format="html"):
+    active_releases = Release.objects.filter(weight__gte=0).order_by("status", "-weight", "-name")
+    old_releases = Release.objects.filter(weight__lt=0).order_by("status", "-weight", "-name")
+    if format in ("json", "xml"):
         from itertools import chain
+
         data = serializers.serialize(format, chain(active_releases, old_releases))
         return HttpResponse(data, content_type=MIME_TYPES[format])
     context = {
-        'pageSection': "releases",
-        'active_releases': active_releases,
-        'old_releases': old_releases,
+        "pageSection": "releases",
+        "active_releases": active_releases,
+        "old_releases": old_releases,
     }
-    return render(request, 'release_list.html', context)
+    return render(request, "release_list.html", context)
 
 
-def release(request, release_name, format='html'):
+def release(request, release_name, format="html"):
     release = get_object_or_404(Release, name=release_name)
-    if format == 'xml':
-        return render(
-            request, 'release_detail.xml', {'release': release}, content_type=MIME_TYPES[format]
-        )
-    context = {
-        'pageSection': "releases",
-        'user_language': get_user_locale(request),
-        'release': release
-    }
-    return render(request, 'release_detail.html', context)
+    if format == "xml":
+        return render(request, "release_detail.xml", {"release": release}, content_type=MIME_TYPES[format])
+    context = {"pageSection": "releases", "user_language": get_user_locale(request), "release": release}
+    return render(request, "release_detail.html", context)
 
 
 def compare_by_releases(request, dtype, rels_to_compare):
@@ -302,28 +308,27 @@ def compare_by_releases(request, dtype, rels_to_compare):
         raise Http404("Wrong domain type")
     releases = list(
         Release.objects.in_bulk(
-            [f'gnome-{rel_name}' for rel_name in rels_to_compare.split("/")],
-            field_name='name'
+            [f"gnome-{rel_name}" for rel_name in rels_to_compare.split("/")], field_name="name"
         ).values()
     )
     if not releases:
         raise Http404("No matching releases")
     stats = Release.total_by_releases(dtype, releases)
     context = {
-        'releases': releases,
-        'stats': stats,
+        "releases": releases,
+        "stats": stats,
     }
-    return render(request, 'release_compare.html', context)
+    return render(request, "release_compare.html", context)
 
 
 # ********** Utility function **********
 def can_refresh_branch(user):
-    """ Return True if user is authorized to force statistics refresh """
+    """Return True if user is authorized to force statistics refresh"""
     if not user.is_authenticated:
         return False
-    if user.has_perm('stats.change_module'):
+    if user.has_perm("stats.change_module"):
         return True
     person = Person.get_by_user(user)
-    if person.is_coordinator(team='any'):
+    if person.is_coordinator(team="any"):
         return True
     return False
diff --git a/teams/admin.py b/teams/admin.py
index ba422850..a804d9c1 100644
--- a/teams/admin.py
+++ b/teams/admin.py
@@ -1,6 +1,7 @@
 from django.contrib import admin
-from teams.models import Team, Role
+
 from languages.models import Language
+from teams.models import Role, Team
 
 
 class LanguageInline(admin.TabularInline):
@@ -10,23 +11,26 @@ class LanguageInline(admin.TabularInline):
 
 
 class TeamAdmin(admin.ModelAdmin):
-    search_fields = ('name', 'description')
-    list_display = ('description', 'use_workflow')
+    search_fields = ("name", "description")
+    list_display = ("description", "use_workflow")
     inlines = [LanguageInline]
 
     def formfield_for_dbfield(self, db_field, **kwargs):
         # Reduced text area for aliases
         field = super().formfield_for_dbfield(db_field, **kwargs)
-        if db_field.name == 'description':
-            field.widget.attrs['rows'] = '4'
+        if db_field.name == "description":
+            field.widget.attrs["rows"] = "4"
 
         return field
 
 
 class RoleAdmin(admin.ModelAdmin):
     search_fields = (
-        'person__first_name', 'person__last_name', 'person__username',
-        'team__description', 'role',
+        "person__first_name",
+        "person__last_name",
+        "person__username",
+        "team__description",
+        "role",
     )
 
 
diff --git a/teams/forms.py b/teams/forms.py
index a64bb4d6..d8d451fd 100644
--- a/teams/forms.py
+++ b/teams/forms.py
@@ -3,18 +3,18 @@ from django.conf import settings
 from django.utils.translation import gettext as _
 
 from common.utils import is_site_admin, send_mail
-from teams.models import Team, Role, ROLE_CHOICES
+from teams.models import ROLE_CHOICES, Role, Team
 
 
 class EditTeamDetailsForm(forms.ModelForm):
     class Meta:
         model = Team
-        fields = ('webpage_url', 'mailing_list', 'mailing_list_subscribe', 'use_workflow', 'presentation')
+        fields = ("webpage_url", "mailing_list", "mailing_list_subscribe", "use_workflow", "presentation")
         widgets = {
-            'webpage_url': forms.TextInput(attrs={'size': 60, 'class': 'form-control'}),
-            'mailing_list': forms.TextInput(attrs={'size': 60, 'class': 'form-control'}),
-            'mailing_list_subscribe': forms.TextInput(attrs={'size': 60, 'class': 'form-control'}),
-            'presentation': forms.Textarea(attrs={'cols': 60, 'class': 'form-control'}),
+            "webpage_url": forms.TextInput(attrs={"size": 60, "class": "form-control"}),
+            "mailing_list": forms.TextInput(attrs={"size": 60, "class": "form-control"}),
+            "mailing_list_subscribe": forms.TextInput(attrs={"size": 60, "class": "form-control"}),
+            "presentation": forms.Textarea(attrs={"cols": 60, "class": "form-control"}),
         }
 
     def __init__(self, user, *args, **kwargs):
@@ -23,14 +23,14 @@ class EditTeamDetailsForm(forms.ModelForm):
         if is_site_admin(user):
             # Add coordinatorship dropdown
             all_members = [
-                (r.id, r.person.name) for r in 
Role.objects.select_related('person').filter(team=self.instance)
+                (r.id, r.person.name) for r in 
Role.objects.select_related("person").filter(team=self.instance)
             ]
-            all_members.insert(0, ('', '-------'))
+            all_members.insert(0, ("", "-------"))
             try:
-                current_coord_pk = Role.objects.filter(team=self.instance, role='coordinator')[0].pk
+                current_coord_pk = Role.objects.filter(team=self.instance, role="coordinator")[0].pk
             except IndexError:
                 current_coord_pk = None
-            self.fields['coordinatorship'] = forms.ChoiceField(
+            self.fields["coordinatorship"] = forms.ChoiceField(
                 label=_("Coordinator"),
                 choices=all_members,
                 required=False,
@@ -39,42 +39,38 @@ class EditTeamDetailsForm(forms.ModelForm):
 
     def save(self, *args, **kwargs):
         super().save(*args, **kwargs)
-        if 'coordinatorship' in self.changed_data and is_site_admin(self.user):
+        if "coordinatorship" in self.changed_data and is_site_admin(self.user):
             # Change coordinator
             try:
                 # Pass current coordinator as committer
-                current_coord = Role.objects.filter(team=self.instance, role='coordinator')[0]
-                current_coord.role = 'committer'
+                current_coord = Role.objects.filter(team=self.instance, role="coordinator")[0]
+                current_coord.role = "committer"
                 current_coord.save()
             except IndexError:
                 pass
-            if self.cleaned_data['coordinatorship']:
-                new_coord = Role.objects.get(pk=self.cleaned_data['coordinatorship'])
-                new_coord.role = 'coordinator'
+            if self.cleaned_data["coordinatorship"]:
+                new_coord = Role.objects.get(pk=self.cleaned_data["coordinatorship"])
+                new_coord.role = "coordinator"
                 new_coord.save()
 
 
 class EditMemberRoleForm(forms.Form):
-
     def __init__(self, roles, *args, **kwargs):
         super().__init__(*args, **kwargs)
-        choices = [x for x in ROLE_CHOICES if x[0] != 'coordinator']
-        choices.extend([
-            ('inactivate', _("Mark as Inactive")),
-            ('remove', _("Remove From Team"))
-        ])
+        choices = [x for x in ROLE_CHOICES if x[0] != "coordinator"]
+        choices.extend([("inactivate", _("Mark as Inactive")), ("remove", _("Remove From Team"))])
         for role in roles:
             self.fields[str(role.pk)] = forms.ChoiceField(
                 choices=choices,
                 label='<a href="%s">%s</a>' % (role.person.get_absolute_url(), role.person.name),
-                initial=role.role
+                initial=role.role,
             )
         if roles:
-            self.fields['form_type'] = forms.CharField(widget=forms.HiddenInput, initial=roles[0].role)
+            self.fields["form_type"] = forms.CharField(widget=forms.HiddenInput, initial=roles[0].role)
 
     def get_fields(self):
         for key in self.fields:
-            if key not in ('form_type',):
+            if key not in ("form_type",):
                 yield self[key]
 
     def save(self, request):
@@ -86,13 +82,13 @@ class EditMemberRoleForm(forms.Form):
                     team = role.team.description
                     role.delete()
                     message = _("You have been removed from the %(team)s team on %(site)s") % {
-                        'team': team,
-                        'site': settings.SITE_DOMAIN,
+                        "team": team,
+                        "site": settings.SITE_DOMAIN,
                     }
                     message += "\n\n" + _("This is an automatic message sent from %(site)s. Please do not 
answer.") % {
-                        'site': settings.SITE_DOMAIN
+                        "site": settings.SITE_DOMAIN
                     }
-                    send_mail(_('Removed from team'), message, to=[role.person.email])
+                    send_mail(_("Removed from team"), message, to=[role.person.email])
                 elif form_value == "inactivate":
                     role.is_active = False
                     role.save()
@@ -100,11 +96,11 @@ class EditMemberRoleForm(forms.Form):
                     role.role = form_value
                     role.save()
                     message = _("Your role in the %(team)s team on %(site)s has been set to “%(role)s”") % {
-                        'team': role.team.description,
-                        'site': settings.SITE_DOMAIN,
-                        'role': role.get_role_display(),
+                        "team": role.team.description,
+                        "site": settings.SITE_DOMAIN,
+                        "role": role.get_role_display(),
                     }
                     message += "\n\n" + _("This is an automatic message sent from %(site)s. Please do not 
answer.") % {
-                        'site': settings.SITE_DOMAIN
+                        "site": settings.SITE_DOMAIN
                     }
-                    send_mail(_('Role changed'), message, to=[role.person.email])
+                    send_mail(_("Role changed"), message, to=[role.person.email])
diff --git a/teams/migrations/0001_initial.py b/teams/migrations/0001_initial.py
index b7422934..bc7979ee 100644
--- a/teams/migrations/0001_initial.py
+++ b/teams/migrations/0001_initial.py
@@ -1,58 +1,65 @@
-from django.db import models, migrations
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('people', '0001_initial'),
+        ("people", "0001_initial"),
     ]
 
     operations = [
         migrations.CreateModel(
-            name='Role',
+            name="Role",
             fields=[
-                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, 
primary_key=True)),
-                ('role', models.CharField(
-                    default='translator', max_length=15,
-                    choices=[
-                        ('translator', 'Translator'), ('reviewer', 'Reviewer'),
-                        ('committer', 'Committer'), ('coordinator', 'Coordinator')
-                    ]
-                )),
-                ('is_active', models.BooleanField(default=True)),
-                ('person', models.ForeignKey(to='people.Person', on_delete=models.CASCADE)),
+                ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, 
primary_key=True)),
+                (
+                    "role",
+                    models.CharField(
+                        default="translator",
+                        max_length=15,
+                        choices=[
+                            ("translator", "Translator"),
+                            ("reviewer", "Reviewer"),
+                            ("committer", "Committer"),
+                            ("coordinator", "Coordinator"),
+                        ],
+                    ),
+                ),
+                ("is_active", models.BooleanField(default=True)),
+                ("person", models.ForeignKey(to="people.Person", on_delete=models.CASCADE)),
             ],
             options={
-                'db_table': 'role',
+                "db_table": "role",
             },
         ),
         migrations.CreateModel(
-            name='Team',
+            name="Team",
             fields=[
-                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, 
primary_key=True)),
-                ('name', models.CharField(max_length=80)),
-                ('description', models.TextField()),
-                ('use_workflow', models.BooleanField(default=True)),
-                ('presentation', models.TextField(verbose_name='Presentation', blank=True)),
-                ('webpage_url', models.URLField(null=True, verbose_name='Web page', blank=True)),
-                ('mailing_list', models.EmailField(
-                    max_length=254, null=True, verbose_name='Mailing list', blank=True
-                )),
-                ('mailing_list_subscribe', models.URLField(null=True, verbose_name='URL to subscribe', 
blank=True)),
-                ('members', models.ManyToManyField(related_name='teams', through='teams.Role', 
to='people.Person')),
+                ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, 
primary_key=True)),
+                ("name", models.CharField(max_length=80)),
+                ("description", models.TextField()),
+                ("use_workflow", models.BooleanField(default=True)),
+                ("presentation", models.TextField(verbose_name="Presentation", blank=True)),
+                ("webpage_url", models.URLField(null=True, verbose_name="Web page", blank=True)),
+                (
+                    "mailing_list",
+                    models.EmailField(max_length=254, null=True, verbose_name="Mailing list", blank=True),
+                ),
+                ("mailing_list_subscribe", models.URLField(null=True, verbose_name="URL to subscribe", 
blank=True)),
+                ("members", models.ManyToManyField(related_name="teams", through="teams.Role", 
to="people.Person")),
             ],
             options={
-                'ordering': ('description',),
-                'db_table': 'team',
+                "ordering": ("description",),
+                "db_table": "team",
             },
         ),
         migrations.AddField(
-            model_name='role',
-            name='team',
-            field=models.ForeignKey(to='teams.Team', on_delete=models.CASCADE),
+            model_name="role",
+            name="team",
+            field=models.ForeignKey(to="teams.Team", on_delete=models.CASCADE),
         ),
         migrations.AlterUniqueTogether(
-            name='role',
-            unique_together=set([('team', 'person')]),
+            name="role",
+            unique_together=set([("team", "person")]),
         ),
     ]
diff --git a/teams/models.py b/teams/models.py
index 0faff942..3816b5e2 100644
--- a/teams/models.py
+++ b/teams/models.py
@@ -4,14 +4,14 @@ from django.conf import settings
 from django.db import models
 from django.urls import reverse
 from django.utils import timezone, translation
-from django.utils.translation import gettext_lazy, gettext as _
+from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy
 
 from common.utils import send_mail
 from people.models import Person
 
 
 class TeamManager(models.Manager):
-
     def all_with_coordinator(self):
         """
         Returns all teams with the coordinator already prefilled. Use that
@@ -20,7 +20,7 @@ class TeamManager(models.Manager):
         roles.
         """
         teams = self.all()
-        roles = Role.objects.select_related("person").filter(role='coordinator')
+        roles = Role.objects.select_related("person").filter(role="coordinator")
         role_dict = {}
 
         for role in roles:
@@ -28,7 +28,7 @@ class TeamManager(models.Manager):
 
         for team in teams:
             try:
-                team.roles = {'coordinator': role_dict[team.id]}
+                team.roles = {"coordinator": role_dict[team.id]}
             except KeyError:
                 # Abnormal because a team must have a coordinator but of no
                 # consequence
@@ -68,15 +68,15 @@ class Team(models.Model):
     description = models.TextField()
     use_workflow = models.BooleanField(default=True)
     presentation = models.TextField(blank=True, verbose_name=_("Presentation"))
-    members = models.ManyToManyField(Person, through='Role', related_name='teams')
+    members = models.ManyToManyField(Person, through="Role", related_name="teams")
     webpage_url = models.URLField(null=True, blank=True, verbose_name=_("Web page"))
     mailing_list = models.EmailField(null=True, blank=True, verbose_name=_("Mailing list"))
     mailing_list_subscribe = models.URLField(null=True, blank=True, verbose_name=_("URL to subscribe"))
     objects = TeamManager()
 
     class Meta:
-        db_table = 'team'
-        ordering = ('description',)
+        db_table = "team"
+        ordering = ("description",)
 
     def __init__(self, *args, **kwargs):
         models.Model.__init__(self, *args, **kwargs)
@@ -86,21 +86,18 @@ class Team(models.Model):
         return self.description
 
     def get_absolute_url(self):
-        return reverse('team_slug', args=[self.name])
+        return reverse("team_slug", args=[self.name])
 
     def can_edit(self, user):
-        """ Return True if user is allowed to edit this team
-            user is a User (from request.user), not a Person
+        """Return True if user is allowed to edit this team
+        user is a User (from request.user), not a Person
         """
         return user.is_authenticated and user.username in [p.username for p in self.get_coordinators()]
 
     def fill_role(self, role, person):
-        """ Used by TeamManager to prefill roles in team """
+        """Used by TeamManager to prefill roles in team"""
         if not self.roles:
-            self.roles = {'coordinator': [],
-                          'committer': [],
-                          'reviewer': [],
-                          'translator': []}
+            self.roles = {"coordinator": [], "committer": [], "reviewer": [], "translator": []}
         self.roles[role].append(person)
 
     def get_description(self):
@@ -110,29 +107,27 @@ class Team(models.Model):
         return self.language_set.all()
 
     def get_members_by_role_exact(self, role, only_active=True):
-        """ Return a list of active members """
+        """Return a list of active members"""
         try:
             return self.roles[role]
         except KeyError:
             if only_active:
-                members = Person.objects.filter(
-                    role__team__id=self.id, role__role=role, role__is_active=True
-                )
+                members = Person.objects.filter(role__team__id=self.id, role__role=role, 
role__is_active=True)
             else:
                 members = Person.objects.filter(role__team__id=self.id, role__role=role)
             return list(members)
 
     def get_coordinators(self):
-        return self.get_members_by_role_exact('coordinator', only_active=False)
+        return self.get_members_by_role_exact("coordinator", only_active=False)
 
     def get_committers_exact(self):
-        return self.get_members_by_role_exact('committer')
+        return self.get_members_by_role_exact("committer")
 
     def get_reviewers_exact(self):
-        return self.get_members_by_role_exact('reviewer')
+        return self.get_members_by_role_exact("reviewer")
 
     def get_translators_exact(self):
-        return self.get_members_by_role_exact('translator')
+        return self.get_members_by_role_exact("translator")
 
     def get_members_by_roles(self, roles, only_active=True):
         """Requires a list of roles in argument"""
@@ -142,24 +137,22 @@ class Team(models.Model):
                 members += self.roles[role]
         except KeyError:
             if only_active:
-                members = Person.objects.filter(
-                    role__team__id=self.id, role__role__in=roles, role__is_active=True
-                )
+                members = Person.objects.filter(role__team__id=self.id, role__role__in=roles, 
role__is_active=True)
             else:
                 members = Person.objects.filter(role__team__id=self.id, role__role__in=roles)
         return list(members)
 
     def get_committers(self):
-        return self.get_members_by_roles(['coordinator', 'committer'])
+        return self.get_members_by_roles(["coordinator", "committer"])
 
     def get_reviewers(self):
-        return self.get_members_by_roles(['coordinator', 'committer', 'reviewer'])
+        return self.get_members_by_roles(["coordinator", "committer", "reviewer"])
 
     def get_translators(self):
         """Don't use get_members_by_roles to provide an optimization"""
         try:
             members = []
-            for role in ['coordinator', 'committer', 'reviewer', 'translator']:
+            for role in ["coordinator", "committer", "reviewer", "translator"]:
                 members += self.roles[role]
         except KeyError:
             # Not necessary to filter as for other roles
@@ -167,24 +160,20 @@ class Team(models.Model):
         return members
 
     def get_inactive_members(self):
-        """ Return the inactive members """
-        members = list(Person.objects.filter(role__team__id=self.id,
-                                             role__is_active=False))
+        """Return the inactive members"""
+        members = list(Person.objects.filter(role__team__id=self.id, role__is_active=False))
         return members
 
     def send_mail_to_coordinator(self, subject, message, messagekw=None):
-        """ Send a message to the coordinator, in her language if available
-            and if subject and message are lazy strings """
+        """Send a message to the coordinator, in her language if available
+        and if subject and message are lazy strings"""
         recipients = [pers.email for pers in self.get_coordinators() if pers.email]
         if not recipients:
             return
         with translation.override(self.language_set.first().locale):
             message = "%s\n--\n" % (message % (messagekw or {}),)
             message += _("This is an automated message sent from %s.") % settings.SITE_DOMAIN
-            send_mail(
-                str(subject), message, to=recipients,
-                headers={settings.EMAIL_HEADER_NAME: "coordinator-mail"}
-            )
+            send_mail(str(subject), message, to=recipients, headers={settings.EMAIL_HEADER_NAME: 
"coordinator-mail"})
 
 
 class FakeTeam:
@@ -192,6 +181,7 @@ class FakeTeam:
     This is a class replacing a Team object when a language
     has no team attached.
     """
+
     fake = 1
 
     def __init__(self, language):
@@ -199,7 +189,7 @@ class FakeTeam:
         self.description = _("No team for locale %s") % self.language.locale
 
     def get_absolute_url(self):
-        return reverse('team_slug', args=[self.language.locale])
+        return reverse("team_slug", args=[self.language.locale])
 
     def can_edit(self, user):
         return False
@@ -215,10 +205,10 @@ class FakeTeam:
 
 
 ROLE_CHOICES = (
-    ('translator', gettext_lazy('Translator')),
-    ('reviewer', gettext_lazy('Reviewer')),
-    ('committer', gettext_lazy('Committer')),
-    ('coordinator', gettext_lazy('Coordinator')),
+    ("translator", gettext_lazy("Translator")),
+    ("reviewer", gettext_lazy("Reviewer")),
+    ("committer", gettext_lazy("Committer")),
+    ("coordinator", gettext_lazy("Coordinator")),
 )
 
 
@@ -227,24 +217,21 @@ class Role(models.Model):
     This is the intermediary class between Person and Team to attribute roles to
     Team members.
     """
+
     team = models.ForeignKey(Team, on_delete=models.CASCADE)
     person = models.ForeignKey(Person, on_delete=models.CASCADE)
-    role = models.CharField(max_length=15, choices=ROLE_CHOICES,
-                            default='translator')
+    role = models.CharField(max_length=15, choices=ROLE_CHOICES, default="translator")
     is_active = models.BooleanField(default=True)
 
     class Meta:
-        db_table = 'role'
-        unique_together = ('team', 'person')
+        db_table = "role"
+        unique_together = ("team", "person")
 
     def __str__(self):
-        return "%s is %s in %s team" % (self.person.name, self.role,
-                                        self.team.description)
+        return "%s is %s in %s team" % (self.person.name, self.role, self.team.description)
 
     @classmethod
     def inactivate_unused_roles(cls):
-        """ Inactivate the roles when login older than 180 days  """
+        """Inactivate the roles when login older than 180 days"""
         last_login = timezone.now() - timedelta(days=30 * 6)
-        cls.objects.filter(
-            person__last_login__lt=last_login, is_active=True
-        ).update(is_active=False)
+        cls.objects.filter(person__last_login__lt=last_login, is_active=True).update(is_active=False)
diff --git a/teams/tests.py b/teams/tests.py
index f244bda1..d5d9acbe 100644
--- a/teams/tests.py
+++ b/teams/tests.py
@@ -7,68 +7,57 @@ from django.urls import reverse
 from django.utils import timezone
 from django.utils.translation import gettext_lazy
 
-from people.models import Person
-from teams.models import Team, Role
 from languages.models import Language
+from people.models import Person
+from teams.models import Role, Team
 
 
 class TeamsAndRolesMixin:
     @classmethod
     def setUpTestData(cls):
         super().setUpTestData()
-        cls.pn = Person(
-            first_name='John', last_name='Nothing',
-            email='jn devnull com', username='jn'
-        )
-        cls.pn.set_password('password')
+        cls.pn = Person(first_name="John", last_name="Nothing", email="jn devnull com", username="jn")
+        cls.pn.set_password("password")
         cls.pn.save()
 
-        cls.pt = Person.objects.create(
-            first_name='John', last_name='Translator',
-            email='jt tf1 com', username='jt'
-        )
+        cls.pt = Person.objects.create(first_name="John", last_name="Translator", email="jt tf1 com", 
username="jt")
 
-        cls.pr = Person.objects.create(
-            first_name='John', last_name='Reviewer',
-            email='jr csa com', username='jr'
-        )
+        cls.pr = Person.objects.create(first_name="John", last_name="Reviewer", email="jr csa com", 
username="jr")
 
         # active person, but in limit date
         cls.pc = Person.objects.create(
-            first_name='John', last_name='Committer',
-            email='jc alinsudesonpleingre fr', username='jc',
-            last_login=timezone.now() - timedelta(days=30 * 6 - 1)
+            first_name="John",
+            last_name="Committer",
+            email="jc alinsudesonpleingre fr",
+            username="jc",
+            last_login=timezone.now() - timedelta(days=30 * 6 - 1),
         )
 
-        cls.pcoo = Person(
-            first_name='John', last_name='Coordinator',
-            email='jcoo imthebigboss fr', username='jcoo'
-        )
-        cls.pcoo.set_password('password')
+        cls.pcoo = Person(first_name="John", last_name="Coordinator", email="jcoo imthebigboss fr", 
username="jcoo")
+        cls.pcoo.set_password("password")
         cls.pcoo.save()
 
-        cls.t = Team.objects.create(
-            name='fr', description='French',
-            mailing_list='french_ml example org'
-        )
+        cls.t = Team.objects.create(name="fr", description="French", mailing_list="french_ml example org")
 
-        cls.t2 = Team.objects.create(name='pt', description='Portuguese')
+        cls.t2 = Team.objects.create(name="pt", description="Portuguese")
 
-        cls.language = Language.objects.create(name='French', locale='fr', team=cls.t)
+        cls.language = Language.objects.create(name="French", locale="fr", team=cls.t)
 
-        Role.objects.bulk_create([
-            Role(team=cls.t, person=cls.pt),
-            Role(team=cls.t2, person=cls.pt, role='reviewer'),
-            Role(team=cls.t, person=cls.pr, role='reviewer'),
-            Role(team=cls.t, person=cls.pc, role='committer'),
-            Role(team=cls.t, person=cls.pcoo, role='coordinator'),
-        ])
+        Role.objects.bulk_create(
+            [
+                Role(team=cls.t, person=cls.pt),
+                Role(team=cls.t2, person=cls.pt, role="reviewer"),
+                Role(team=cls.t, person=cls.pr, role="reviewer"),
+                Role(team=cls.t, person=cls.pc, role="committer"),
+                Role(team=cls.t, person=cls.pcoo, role="coordinator"),
+            ]
+        )
 
 
 class TeamTests(TeamsAndRolesMixin, TestCase):
     def test_get_members_by_role_exact(self):
-        members = self.t.get_members_by_role_exact('committer')
-        t = Team.objects.get(name='fr')
+        members = self.t.get_members_by_role_exact("committer")
+        t = Team.objects.get(name="fr")
         self.assertEqual(len(members), 1)
         self.assertEqual(members[0], self.pc)
 
@@ -76,14 +65,14 @@ class TeamTests(TeamsAndRolesMixin, TestCase):
         role.is_active = False
         role.save()
 
-        members = self.t.get_members_by_role_exact('committer')
+        members = self.t.get_members_by_role_exact("committer")
         self.assertEqual(len(members), 0)
 
     def test_get_inactive_members(self):
         members = self.t.get_inactive_members()
         self.assertEqual(len(members), 0)
 
-        t = Team.objects.get(name='fr')
+        t = Team.objects.get(name="fr")
         role = Role.objects.get(person=self.pc, team=t)
         role.is_active = False
         role.save()
@@ -146,16 +135,13 @@ class TeamTests(TeamsAndRolesMixin, TestCase):
         self.run_roles_test(Team.objects.all_with_roles()[0])
 
     def test_join_team(self):
-        response = self.client.post(
-            '/login/',
-            data={'username': self.pn.username, 'password': 'password'}
-        )
+        response = self.client.post("/login/", data={"username": self.pn.username, "password": "password"})
         # Display team join page
-        team_join_url = reverse('person_team_join', current_app='people')
+        team_join_url = reverse("person_team_join", current_app="people")
         response = self.client.get(team_join_url)
         self.assertContains(response, "<select ")
         # Post for joining
-        response = self.client.post(team_join_url, {'teams': [str(self.t.pk)]})
+        response = self.client.post(team_join_url, {"teams": [str(self.t.pk)]})
         # Test user is member of team
         self.assertTrue(self.pn.is_translator(self.t))
         # Test coordinator receives email
@@ -164,96 +150,98 @@ class TeamTests(TeamsAndRolesMixin, TestCase):
         # Mail should be sent in the target team's language (i.e. French here)
         self.assertIn("rejoindre", mail.outbox[0].body)
         # Double submission should not crash
-        self.client.post(team_join_url, {'teams': [str(self.t.pk)]})
+        self.client.post(team_join_url, {"teams": [str(self.t.pk)]})
 
     def test_leave_team(self):
-        Role.objects.create(team=self.t, person=self.pn, role='translator')
-        self.client.login(username='jn', password='password')
-        response = self.client.post(reverse('person_team_leave', args=[self.t.name]))
-        self.assertRedirects(response, reverse('person_detail_username', args=[self.pn.username]))
+        Role.objects.create(team=self.t, person=self.pn, role="translator")
+        self.client.login(username="jn", password="password")
+        response = self.client.post(reverse("person_team_leave", args=[self.t.name]))
+        self.assertRedirects(response, reverse("person_detail_username", args=[self.pn.username]))
         self.pn.refresh_from_db()
         self.assertEqual(self.pn.role_set.count(), 0)
 
     def test_edit_team(self):
-        """ Test team edit form """
-        edit_url = reverse('team_edit', args=['fr'], current_app='teams')
-        with self.assertLogs('django.request', level='WARNING'):
+        """Test team edit form"""
+        edit_url = reverse("team_edit", args=["fr"], current_app="teams")
+        with self.assertLogs("django.request", level="WARNING"):
             response = self.client.get(edit_url)
         self.assertEqual(response.status_code, 403)
         # Login as team coordinator
+        response = self.client.post("/login/", data={"username": self.pcoo.username, "password": "password"})
+        # Try team modification
         response = self.client.post(
-            '/login/',
-            data={'username': self.pcoo.username, 'password': 'password'}
+            edit_url,
+            {
+                "webpage_url": "http://www.gnomefr.org/";,
+                "mailing_list": "gnomefr traduc org",
+                "mailing_list_subscribe": "",
+            },
         )
-        # Try team modification
-        response = self.client.post(edit_url, {
-            'webpage_url': "http://www.gnomefr.org/";,
-            'mailing_list': "gnomefr traduc org",
-            'mailing_list_subscribe': ""
-        })
-        team = Team.objects.get(name='fr')
+        team = Team.objects.get(name="fr")
         self.assertEqual(team.webpage_url, "http://www.gnomefr.org/";)
 
     def test_edit_team_roles(self):
-        team_url = reverse('team_slug', args=['fr'])
+        team_url = reverse("team_slug", args=["fr"])
         # Login as team coordinator
         self.client.force_login(self.pcoo)
         # Team member role modification
-        self.client.post(team_url, {
-            'form_type': 'reviewer',
-            '%d' % Role.objects.get(team=self.t, person=self.pr).pk: 'committer',
-        })
+        self.client.post(
+            team_url,
+            {
+                "form_type": "reviewer",
+                "%d" % Role.objects.get(team=self.t, person=self.pr).pk: "committer",
+            },
+        )
         self.assertEqual(len(mail.outbox), 1)
-        self.assertIn('Role changed', mail.outbox[0].subject)
+        self.assertIn("Role changed", mail.outbox[0].subject)
         self.assertIn(
             "Your role in the French team on %s has been set to “Committer”" % settings.SITE_DOMAIN,
-            mail.outbox[0].body
+            mail.outbox[0].body,
         )
         mail.outbox = []
         # Team member removal
-        self.client.post(team_url, {
-            'form_type': 'translator',
-            '%d' % Role.objects.get(team=self.t, person=self.pt).pk: 'remove',
-        })
-        self.assertEqual(len(mail.outbox), 1)
-        self.assertIn('Removed from team', mail.outbox[0].subject)
-        self.assertIn(
-            "You have been removed from the French team on %s" % settings.SITE_DOMAIN,
-            mail.outbox[0].body
+        self.client.post(
+            team_url,
+            {
+                "form_type": "translator",
+                "%d" % Role.objects.get(team=self.t, person=self.pt).pk: "remove",
+            },
         )
+        self.assertEqual(len(mail.outbox), 1)
+        self.assertIn("Removed from team", mail.outbox[0].subject)
+        self.assertIn("You have been removed from the French team on %s" % settings.SITE_DOMAIN, 
mail.outbox[0].body)
 
     def test_send_mail_to_coordinator(self):
         self.t.send_mail_to_coordinator(subject="foo", message="bar")
         self.assertEqual(len(mail.outbox), 1)
         self.assertEqual(mail.outbox[0].subject, "%sfoo" % settings.EMAIL_SUBJECT_PREFIX)
-        self.assertEqual(mail.outbox[0].extra_headers, {settings.EMAIL_HEADER_NAME: 'coordinator-mail'})
+        self.assertEqual(mail.outbox[0].extra_headers, {settings.EMAIL_HEADER_NAME: "coordinator-mail"})
         # the message is sent in the language of the team
         self.t.send_mail_to_coordinator(subject=gettext_lazy("About Damned Lies"), message="...")
         self.assertEqual(len(mail.outbox), 2)
-        self.assertEqual(
-            mail.outbox[1].subject,
-            "%sÀ propos de Damned Lies" % settings.EMAIL_SUBJECT_PREFIX
-        )
+        self.assertEqual(mail.outbox[1].subject, "%sÀ propos de Damned Lies" % settings.EMAIL_SUBJECT_PREFIX)
 
 
 class JSONTeamsTest(TeamsAndRolesMixin, TestCase):
     def setUp(self):
         super().setUp()
-        t3 = Team.objects.create(name='gl', description='Galician')
+        t3 = Team.objects.create(name="gl", description="Galician")
         coor1 = Person.objects.create(
-            first_name='Marcos', last_name='Coordinator',
-            email='marc imthebigboss fr', username='marcos', svn_account='thesvnaccount'
+            first_name="Marcos",
+            last_name="Coordinator",
+            email="marc imthebigboss fr",
+            username="marcos",
+            svn_account="thesvnaccount",
         )
         coor2 = Person.objects.create(
-            first_name='Pepe', last_name='Coordinator',
-            email='pepe imthebigboss es', username='pepe'
+            first_name="Pepe", last_name="Coordinator", email="pepe imthebigboss es", username="pepe"
         )
-        Role.objects.create(team=t3, person=coor1, role='coordinator')
-        Role.objects.create(team=t3, person=coor2, role='coordinator')
+        Role.objects.create(team=t3, person=coor1, role="coordinator")
+        Role.objects.create(team=t3, person=coor2, role="coordinator")
 
     def test_json_teams(self):
         """Test JSON teams interface"""
-        response = self.client.get(reverse('teams', args=['json']))
+        response = self.client.get(reverse("teams", args=["json"]))
         self.assertEqual(response.status_code, 200)
         expected_json = """[
             {
@@ -283,11 +271,10 @@ class JSONTeamsTest(TeamsAndRolesMixin, TestCase):
                 "coordinators": []
             }
             ]"""
-        self.assertJSONEqual(response.content.decode('utf-8'), expected_json)
+        self.assertJSONEqual(response.content.decode("utf-8"), expected_json)
 
 
 class RoleTest(TeamsAndRolesMixin, TestCase):
-
     def setUp(self):
         super().setUp()
 
diff --git a/teams/urls.py b/teams/urls.py
index 65223a3b..aa6177fa 100644
--- a/teams/urls.py
+++ b/teams/urls.py
@@ -2,15 +2,8 @@ from django.urls import path, re_path
 
 from teams import views
 
-
 urlpatterns = [
-    re_path(r'^(?P<format>(xml|json))?/?$',
-            views.teams,
-            name='teams'),
-    path('<locale:team_slug>/',
-         views.team,
-         name='team_slug'),
-    path('<locale:team_slug>/edit/',
-         views.team_edit,
-         name='team_edit'),
+    re_path(r"^(?P<format>(xml|json))?/?$", views.teams, name="teams"),
+    path("<locale:team_slug>/", views.team, name="team_slug"),
+    path("<locale:team_slug>/edit/", views.team_edit, name="team_edit"),
 ]
diff --git a/teams/views.py b/teams/views.py
index 3d11d6f0..41061fed 100644
--- a/teams/views.py
+++ b/teams/views.py
@@ -1,27 +1,24 @@
-from django.http import HttpResponseRedirect, HttpResponseForbidden
-from django.shortcuts import render, get_object_or_404
+from django.http import HttpResponseForbidden, HttpResponseRedirect
+from django.shortcuts import get_object_or_404, render
 from django.urls import reverse
 from django.utils.translation import gettext_lazy as _
 
 from common import utils
-from teams.models import Team, FakeTeam, Role
-from teams.forms import EditMemberRoleForm, EditTeamDetailsForm
 from languages.models import Language
+from teams.forms import EditMemberRoleForm, EditTeamDetailsForm
+from teams.models import FakeTeam, Role, Team
 
 
-def teams(request, format='html'):
+def teams(request, format="html"):
     teams = Team.objects.all_with_coordinator()
-    format = request.GET.get('format') or format
-    if format in ('xml', 'json'):
-        return render(
-            request, 'teams/team_list.%s' % format, {'teams': teams},
-            content_type=utils.MIME_TYPES[format]
-        )
+    format = request.GET.get("format") or format
+    if format in ("xml", "json"):
+        return render(request, "teams/team_list.%s" % format, {"teams": teams}, 
content_type=utils.MIME_TYPES[format])
     context = {
-        'pageSection': 'teams',
-        'teams': utils.trans_sort_object_list(teams, 'description'),
+        "pageSection": "teams",
+        "teams": utils.trans_sort_object_list(teams, "description"),
     }
-    return render(request, 'teams/team_list.html', context)
+    return render(request, "teams/team_list.html", context)
 
 
 def team(request, team_slug):
@@ -29,33 +26,33 @@ def team(request, team_slug):
         team = Team.objects.get(name=team_slug)
         mem_groups = (
             {
-                'id': 'committers',
-                'title': _("Committers"),
-                'members': team.get_committers_exact(),
-                'form': None,
-                'no_member': _("No committers")
+                "id": "committers",
+                "title": _("Committers"),
+                "members": team.get_committers_exact(),
+                "form": None,
+                "no_member": _("No committers"),
             },
             {
-                'id': 'reviewers',
-                'title': _("Reviewers"),
-                'members': team.get_reviewers_exact(),
-                'form': None,
-                'no_member': _("No reviewers")
+                "id": "reviewers",
+                "title": _("Reviewers"),
+                "members": team.get_reviewers_exact(),
+                "form": None,
+                "no_member": _("No reviewers"),
             },
             {
-                'id': 'translators',
-                'title': _("Translators"),
-                'members': team.get_translators_exact(),
-                'form': None,
-                'no_member': _("No translators")
+                "id": "translators",
+                "title": _("Translators"),
+                "members": team.get_translators_exact(),
+                "form": None,
+                "no_member": _("No translators"),
             },
             {
-                'id': 'inactive',
-                'title': _("Inactive members"),
-                'members': team.get_inactive_members(),
-                'form': None,
-                'no_member': _("No inactive members")
-            }
+                "id": "inactive",
+                "title": _("Inactive members"),
+                "members": team.get_inactive_members(),
+                "form": None,
+                "no_member": _("No inactive members"),
+            },
         )
     except Team.DoesNotExist:
         lang = get_object_or_404(Language, locale=team_slug)
@@ -63,32 +60,32 @@ def team(request, team_slug):
         mem_groups = ()
 
     context = {
-        'pageSection': 'teams',
-        'team': team,
-        'can_edit_team': False,
+        "pageSection": "teams",
+        "team": team,
+        "can_edit_team": False,
     }
     if team.can_edit(request.user):
-        if request.method == 'POST':
-            form_type = request.POST['form_type']
+        if request.method == "POST":
+            form_type = request.POST["form_type"]
             roles = Role.objects.filter(team=team, role=form_type, is_active=True)
             form = EditMemberRoleForm(roles, request.POST)
             if form.is_valid():
                 form.save(request)
         # Create forms for template
-        commit_roles = Role.objects.filter(team=team, role='committer', is_active=True)
+        commit_roles = Role.objects.filter(team=team, role="committer", is_active=True)
         if commit_roles:
-            mem_groups[0]['form'] = EditMemberRoleForm(commit_roles)
-        review_roles = Role.objects.filter(team=team, role='reviewer', is_active=True)
+            mem_groups[0]["form"] = EditMemberRoleForm(commit_roles)
+        review_roles = Role.objects.filter(team=team, role="reviewer", is_active=True)
         if review_roles:
-            mem_groups[1]['form'] = EditMemberRoleForm(review_roles)
-        translate_roles = Role.objects.filter(team=team, role='translator', is_active=True)
+            mem_groups[1]["form"] = EditMemberRoleForm(review_roles)
+        translate_roles = Role.objects.filter(team=team, role="translator", is_active=True)
         if translate_roles:
-            mem_groups[2]['form'] = EditMemberRoleForm(translate_roles)
-        context['can_edit_team'] = True
+            mem_groups[2]["form"] = EditMemberRoleForm(translate_roles)
+        context["can_edit_team"] = True
 
-    context['mem_groups'] = mem_groups
-    context['can_edit_details'] = context['can_edit_team'] or utils.is_site_admin(request.user)
-    return render(request, 'teams/team_detail.html', context)
+    context["mem_groups"] = mem_groups
+    context["can_edit_details"] = context["can_edit_team"] or utils.is_site_admin(request.user)
+    return render(request, "teams/team_detail.html", context)
 
 
 def team_edit(request, team_slug):
@@ -96,12 +93,9 @@ def team_edit(request, team_slug):
     if not (team.can_edit(request.user) or utils.is_site_admin(request.user)):
         return HttpResponseForbidden("You are not allowed to edit this team.")
     form = EditTeamDetailsForm(request.user, request.POST or None, instance=team)
-    if request.method == 'POST':
+    if request.method == "POST":
         if form.is_valid():
             form.save()
-            return HttpResponseRedirect(reverse('team_slug', args=[team_slug]))
-    context = {
-        'team': team,
-        'form': form
-    }
-    return render(request, 'teams/team_edit.html', context)
+            return HttpResponseRedirect(reverse("team_slug", args=[team_slug]))
+    context = {"team": team, "form": form}
+    return render(request, "teams/team_edit.html", context)
diff --git a/vertimus/admin.py b/vertimus/admin.py
index 4fec3f3b..1a0bc612 100644
--- a/vertimus/admin.py
+++ b/vertimus/admin.py
@@ -1,18 +1,22 @@
 from django.contrib import admin
 
-from vertimus.models import State, Action
+from vertimus.models import Action, State
 
 
 class StateAdmin(admin.ModelAdmin):
-    list_filter = ('name', 'language')
-    raw_id_fields = ('branch', 'domain', 'person',)
-    search_fields = ('branch__module__name',)
+    list_filter = ("name", "language")
+    raw_id_fields = (
+        "branch",
+        "domain",
+        "person",
+    )
+    search_fields = ("branch__module__name",)
 
 
 class ActionAdmin(admin.ModelAdmin):
-    list_display = ('__str__', 'state_db', 'merged_file')
-    raw_id_fields = ('state_db', 'person', 'merged_file')
-    search_fields = ('comment',)
+    list_display = ("__str__", "state_db", "merged_file")
+    raw_id_fields = ("state_db", "person", "merged_file")
+    search_fields = ("comment",)
 
 
 admin.site.register(State, StateAdmin)
diff --git a/vertimus/feeds.py b/vertimus/feeds.py
index d1a5587b..1a0f6e07 100644
--- a/vertimus/feeds.py
+++ b/vertimus/feeds.py
@@ -9,15 +9,16 @@ from vertimus.models import Action, ActionArchived
 
 
 class LatestActionsByLanguage(Feed):
-    title_template = 'feeds/actions_title.html'
-    description_template = 'feeds/actions_description.html'
+    title_template = "feeds/actions_title.html"
+    description_template = "feeds/actions_description.html"
 
     def get_object(self, request, locale):
         return Language.objects.get(locale=locale)
 
     def title(self, obj):
         return _("%(site)s — Workflow actions for the %(lang)s language") % {
-            'site': settings.SITE_DOMAIN, 'lang': obj.name
+            "site": settings.SITE_DOMAIN,
+            "lang": obj.name,
         }
 
     def link(self, obj):
@@ -29,20 +30,25 @@ class LatestActionsByLanguage(Feed):
         return _("Latest actions of the GNOME Translation Project for the %s language") % obj.name
 
     def items(self, obj):
-        actions = Action.objects.filter(state_db__language=obj.id).select_related(
-            'state_db'
-        ).union(
-            
ActionArchived.objects.filter(state_db__language=obj.id).defer('sequence').select_related('state_db')
+        actions = (
+            Action.objects.filter(state_db__language=obj.id)
+            .select_related("state_db")
+            .union(
+                
ActionArchived.objects.filter(state_db__language=obj.id).defer("sequence").select_related("state_db")
+            )
         )
-        return actions.order_by('-created')[:20]
+        return actions.order_by("-created")[:20]
 
     def item_link(self, item):
-        link = reverse('vertimus_by_names', args=(
-            item.state_db.branch.module.name,
-            item.state_db.branch.name,
-            item.state_db.domain.name,
-            item.state_db.language.locale,
-        ))
+        link = reverse(
+            "vertimus_by_names",
+            args=(
+                item.state_db.branch.module.name,
+                item.state_db.branch.name,
+                item.state_db.domain.name,
+                item.state_db.language.locale,
+            ),
+        )
         return "%s#%d" % (link, item.id)
 
     def item_pubdate(self, item):
@@ -53,16 +59,14 @@ class LatestActionsByLanguage(Feed):
 
 
 class LatestActionsByTeam(Feed):
-    title_template = 'feeds/actions_title.html'
-    description_template = 'feeds/actions_description.html'
+    title_template = "feeds/actions_title.html"
+    description_template = "feeds/actions_description.html"
 
     def get_object(self, request, team_name):
         return Team.objects.get(name=team_name)
 
     def title(self, obj):
-        return _("%(site)s — Workflow actions of the %(lang)s team") % {
-            'site': settings.SITE_DOMAIN, 'lang': obj
-        }
+        return _("%(site)s — Workflow actions of the %(lang)s team") % {"site": settings.SITE_DOMAIN, 
"lang": obj}
 
     def link(self, obj):
         if not obj:
@@ -73,20 +77,27 @@ class LatestActionsByTeam(Feed):
         return _("Latest actions made by the %s team of the GNOME Translation Project") % obj
 
     def items(self, obj):
-        actions = Action.objects.filter(state_db__language__team=obj.id).select_related(
-            'state_db'
-        ).union(
-            
ActionArchived.objects.filter(state_db__language__team=obj.id).defer('sequence').select_related('state_db')
+        actions = (
+            Action.objects.filter(state_db__language__team=obj.id)
+            .select_related("state_db")
+            .union(
+                ActionArchived.objects.filter(state_db__language__team=obj.id)
+                .defer("sequence")
+                .select_related("state_db")
+            )
         )
-        return actions.order_by('-created')[:20]
+        return actions.order_by("-created")[:20]
 
     def item_link(self, item):
-        link = reverse('vertimus_by_names', args=(
-            item.state_db.branch.module.name,
-            item.state_db.branch.name,
-            item.state_db.domain.name,
-            item.state_db.language.locale,
-        ))
+        link = reverse(
+            "vertimus_by_names",
+            args=(
+                item.state_db.branch.module.name,
+                item.state_db.branch.name,
+                item.state_db.domain.name,
+                item.state_db.language.locale,
+            ),
+        )
         return "%s#%d" % (link, item.id)
 
     def item_pubdate(self, item):
diff --git a/vertimus/forms.py b/vertimus/forms.py
index 97c7438b..278df385 100644
--- a/vertimus/forms.py
+++ b/vertimus/forms.py
@@ -3,11 +3,12 @@ import os
 from django import forms
 from django.core.exceptions import ValidationError
 from django.urls import reverse
-from django.utils.translation import gettext, gettext_lazy as _
+from django.utils.translation import gettext
+from django.utils.translation import gettext_lazy as _
 
-from vertimus.models import Action, ActionCI, ActionSeparator
 from stats.models import Person
 from stats.utils import check_po_conformity
+from vertimus.models import Action, ActionCI, ActionSeparator
 
 
 class DisabledLabel(str):
@@ -16,13 +17,14 @@ class DisabledLabel(str):
 
 class DisablableSelect(forms.Select):
     """Custom widget to allow a Select option to be disabled."""
+
     def create_option(self, *args, **kwargs):
         context = super().create_option(*args, **kwargs)
-        if isinstance(context['label'], DisabledLabel):
-            context['attrs']['disabled'] = True
-            if context['selected']:
-                context['selected'] = False
-                del context['attrs']['selected']
+        if isinstance(context["label"], DisabledLabel):
+            context["attrs"]["disabled"] = True
+            if context["selected"]:
+                context["selected"] = False
+                del context["attrs"]["selected"]
         return context
 
 
@@ -31,63 +33,58 @@ class AuthorChoiceField(forms.ModelChoiceField):
 
     def label_from_instance(self, obj):
         if str(obj) == obj.username:
-            return DisabledLabel(gettext("%(name)s (full name missing)") % {'name': obj.username})
+            return DisabledLabel(gettext("%(name)s (full name missing)") % {"name": obj.username})
         if not obj.email:
-            return DisabledLabel(gettext("%(name)s (email missing)") % {'name': str(obj)})
+            return DisabledLabel(gettext("%(name)s (email missing)") % {"name": str(obj)})
         return str(obj)
 
 
 class ActionForm(forms.Form):
-    action = forms.ChoiceField(
-        label=_("Action"),
-        choices=(),
-        widget=DisablableSelect
-    )
+    action = forms.ChoiceField(label=_("Action"), choices=(), widget=DisablableSelect)
     comment = forms.CharField(
         label=_("Comment"),
         max_length=5000,
         required=False,
-        widget=forms.Textarea(attrs={'rows': 8, 'cols': 70, 'class': 'form-control'})
+        widget=forms.Textarea(attrs={"rows": 8, "cols": 70, "class": "form-control"}),
     )
     author = AuthorChoiceField(label=_("Commit author"), queryset=Person.objects.none(), required=False)
     sync_master = forms.BooleanField(required=False)
-    file = forms.FileField(label=_("File"), required=False,
-                           help_text=_("Upload a .po, .gz, .bz2, .xz or .png file"))
+    file = forms.FileField(label=_("File"), required=False, help_text=_("Upload a .po, .gz, .bz2, .xz or 
.png file"))
     send_to_ml = forms.BooleanField(label=_("Send message to the team mailing list"), required=False)
 
     def __init__(self, current_user, state, actions, has_mailing_list, *args, **kwargs):
         super().__init__(*args, **kwargs)
         self.actions = actions
         self.current_user = current_user
-        self.fields['action'].choices = [(
-            act.name,
-            DisabledLabel(act.description) if isinstance(act, ActionSeparator) else act.description
-        ) for act in actions]
-        self.fields['action'].help_link = reverse('help', args=['vertimus_workflow', 1])
+        self.fields["action"].choices = [
+            (act.name, DisabledLabel(act.description) if isinstance(act, ActionSeparator) else 
act.description)
+            for act in actions
+        ]
+        self.fields["action"].help_link = reverse("help", args=["vertimus_workflow", 1])
         if state and ActionCI in map(type, self.actions):
-            self.fields['author'].queryset = state.involved_persons(
-                extra_user=current_user
-            ).order_by('last_name', 'username')
-            self.fields['author'].initial = state.get_latest_po_file_action().person
+            self.fields["author"].queryset = state.involved_persons(extra_user=current_user).order_by(
+                "last_name", "username"
+            )
+            self.fields["author"].initial = state.get_latest_po_file_action().person
         if not has_mailing_list:
-            del self.fields['send_to_ml']
+            del self.fields["send_to_ml"]
         if state and state.branch.is_head():
-            del self.fields['sync_master']
+            del self.fields["sync_master"]
         elif state:
             main_branch = state.branch.module.get_head_branch()
-            self.fields['sync_master'].label = _("Sync with %(name)s") % {'name': main_branch.name}
-            self.fields['sync_master'].help_text = (
-                _("Try to cherry-pick the commit to the %(name)s branch") % {'name': main_branch.name}
-            )
+            self.fields["sync_master"].label = _("Sync with %(name)s") % {"name": main_branch.name}
+            self.fields["sync_master"].help_text = _("Try to cherry-pick the commit to the %(name)s branch") 
% {
+                "name": main_branch.name
+            }
 
     def clean_file(self):
-        data = self.cleaned_data['file']
+        data = self.cleaned_data["file"]
         if data:
             ext = os.path.splitext(data.name)[1]
-            if ext not in ('.po', '.gz', '.bz2', '.xz', '.png'):
+            if ext not in (".po", ".gz", ".bz2", ".xz", ".png"):
                 raise ValidationError(_("Only files with extension .po, .gz, .bz2, .xz or .png are 
admitted."))
             # If this is a .po file, check validity (msgfmt)
-            if ext == '.po':
+            if ext == ".po":
                 if check_po_conformity(data):
                     raise ValidationError(
                         _(".po file does not pass “msgfmt -vc”. Please correct the file and try again.")
@@ -96,16 +93,16 @@ class ActionForm(forms.Form):
 
     def clean(self):
         cleaned_data = self.cleaned_data
-        action_code = cleaned_data.get('action')
+        action_code = cleaned_data.get("action")
         if action_code is None:
             raise ValidationError(_("Invalid action. Someone probably posted another action just before 
you."))
-        if action_code == 'CI':
-            if not cleaned_data['author']:
+        if action_code == "CI":
+            if not cleaned_data["author"]:
                 raise ValidationError(_("Committing a file requires a commit author."))
-            if 'Token' in getattr(self.current_user, 'backend', ''):
+            if "Token" in getattr(self.current_user, "backend", ""):
                 raise ValidationError(_("Committing a file with token-based authentication is prohibited."))
-        comment = cleaned_data.get('comment')
-        file = cleaned_data.get('file')
+        comment = cleaned_data.get("comment")
+        file = cleaned_data.get("file")
         action = Action.new_by_name(action_code, comment=comment, file=file)
 
         if action.comment_is_required and not comment:
diff --git a/vertimus/migrations/0001_initial.py b/vertimus/migrations/0001_initial.py
index b33a9a77..50f31c21 100644
--- a/vertimus/migrations/0001_initial.py
+++ b/vertimus/migrations/0001_initial.py
@@ -1,271 +1,251 @@
-from django.db import models, migrations
+from django.db import migrations, models
+
 import vertimus.models
 
 
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('stats', '__first__'),
-        ('people', '__first__'),
-        ('languages', '__first__'),
+        ("stats", "__first__"),
+        ("people", "__first__"),
+        ("languages", "__first__"),
     ]
 
     operations = [
         migrations.CreateModel(
-            name='Action',
+            name="Action",
             fields=[
-                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, 
primary_key=True)),
-                ('name', models.SlugField(max_length=8)),
-                ('created', models.DateTimeField(editable=False)),
-                ('comment', models.TextField(null=True, blank=True)),
-                ('file', models.FileField(null=True, upload_to=vertimus.models.generate_upload_filename, 
blank=True)),
+                ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, 
primary_key=True)),
+                ("name", models.SlugField(max_length=8)),
+                ("created", models.DateTimeField(editable=False)),
+                ("comment", models.TextField(null=True, blank=True)),
+                ("file", models.FileField(null=True, upload_to=vertimus.models.generate_upload_filename, 
blank=True)),
                 (
-                    'merged_file',
-                    models.OneToOneField(null=True, blank=True, to='stats.PoFile', on_delete=models.SET_NULL)
+                    "merged_file",
+                    models.OneToOneField(null=True, blank=True, to="stats.PoFile", 
on_delete=models.SET_NULL),
                 ),
-                ('person', models.ForeignKey(to='people.Person', on_delete=models.CASCADE)),
+                ("person", models.ForeignKey(to="people.Person", on_delete=models.CASCADE)),
             ],
             options={
-                'db_table': 'action',
-                'verbose_name': 'action',
+                "db_table": "action",
+                "verbose_name": "action",
             },
         ),
         migrations.CreateModel(
-            name='ActionArchived',
+            name="ActionArchived",
             fields=[
-                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, 
primary_key=True)),
-                ('name', models.SlugField(max_length=8)),
-                ('created', models.DateTimeField(editable=False)),
-                ('comment', models.TextField(null=True, blank=True)),
-                ('file', models.FileField(null=True, upload_to=vertimus.models.generate_upload_filename, 
blank=True)),
-                ('sequence', models.IntegerField(null=True)),
+                ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, 
primary_key=True)),
+                ("name", models.SlugField(max_length=8)),
+                ("created", models.DateTimeField(editable=False)),
+                ("comment", models.TextField(null=True, blank=True)),
+                ("file", models.FileField(null=True, upload_to=vertimus.models.generate_upload_filename, 
blank=True)),
+                ("sequence", models.IntegerField(null=True)),
                 (
-                    'merged_file',
-                    models.OneToOneField(null=True, blank=True, to='stats.PoFile', on_delete=models.SET_NULL)
+                    "merged_file",
+                    models.OneToOneField(null=True, blank=True, to="stats.PoFile", 
on_delete=models.SET_NULL),
                 ),
-                ('person', models.ForeignKey(to='people.Person', on_delete=models.CASCADE)),
+                ("person", models.ForeignKey(to="people.Person", on_delete=models.CASCADE)),
             ],
             options={
-                'db_table': 'action_archived',
+                "db_table": "action_archived",
             },
         ),
         migrations.CreateModel(
-            name='State',
+            name="State",
             fields=[
-                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, 
primary_key=True)),
-                ('name', models.SlugField(default='None', max_length=20)),
-                ('updated', models.DateTimeField(auto_now=True)),
-                ('branch', models.ForeignKey(to='stats.Branch', on_delete=models.CASCADE)),
-                ('domain', models.ForeignKey(to='stats.Domain', on_delete=models.CASCADE)),
-                ('language', models.ForeignKey(to='languages.Language', on_delete=models.CASCADE)),
-                ('person', models.ForeignKey(default=None, to='people.Person', null=True, 
on_delete=models.SET_NULL)),
+                ("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, 
primary_key=True)),
+                ("name", models.SlugField(default="None", max_length=20)),
+                ("updated", models.DateTimeField(auto_now=True)),
+                ("branch", models.ForeignKey(to="stats.Branch", on_delete=models.CASCADE)),
+                ("domain", models.ForeignKey(to="stats.Domain", on_delete=models.CASCADE)),
+                ("language", models.ForeignKey(to="languages.Language", on_delete=models.CASCADE)),
+                ("person", models.ForeignKey(default=None, to="people.Person", null=True, 
on_delete=models.SET_NULL)),
             ],
             options={
-                'db_table': 'state',
-                'verbose_name': 'state',
+                "db_table": "state",
+                "verbose_name": "state",
             },
         ),
         migrations.AddField(
-            model_name='actionarchived',
-            name='state_db',
-            field=models.ForeignKey(to='vertimus.State', on_delete=models.CASCADE),
+            model_name="actionarchived",
+            name="state_db",
+            field=models.ForeignKey(to="vertimus.State", on_delete=models.CASCADE),
         ),
         migrations.AddField(
-            model_name='action',
-            name='state_db',
-            field=models.ForeignKey(to='vertimus.State', on_delete=models.CASCADE),
+            model_name="action",
+            name="state_db",
+            field=models.ForeignKey(to="vertimus.State", on_delete=models.CASCADE),
         ),
         migrations.CreateModel(
-            name='ActionAA',
-            fields=[
-            ],
+            name="ActionAA",
+            fields=[],
             options={
-                'proxy': True,
+                "proxy": True,
             },
-            bases=('vertimus.action',),
+            bases=("vertimus.action",),
         ),
         migrations.CreateModel(
-            name='ActionCI',
-            fields=[
-            ],
+            name="ActionCI",
+            fields=[],
             options={
-                'proxy': True,
+                "proxy": True,
             },
-            bases=('vertimus.action',),
+            bases=("vertimus.action",),
         ),
         migrations.CreateModel(
-            name='ActionIC',
-            fields=[
-            ],
+            name="ActionIC",
+            fields=[],
             options={
-                'proxy': True,
+                "proxy": True,
             },
-            bases=('vertimus.action',),
+            bases=("vertimus.action",),
         ),
         migrations.CreateModel(
-            name='ActionRC',
-            fields=[
-            ],
+            name="ActionRC",
+            fields=[],
             options={
-                'proxy': True,
+                "proxy": True,
             },
-            bases=('vertimus.action',),
+            bases=("vertimus.action",),
         ),
         migrations.CreateModel(
-            name='ActionRP',
-            fields=[
-            ],
+            name="ActionRP",
+            fields=[],
             options={
-                'proxy': True,
+                "proxy": True,
             },
-            bases=('vertimus.action',),
+            bases=("vertimus.action",),
         ),
         migrations.CreateModel(
-            name='ActionRT',
-            fields=[
-            ],
+            name="ActionRT",
+            fields=[],
             options={
-                'proxy': True,
+                "proxy": True,
             },
-            bases=('vertimus.action',),
+            bases=("vertimus.action",),
         ),
         migrations.CreateModel(
-            name='ActionTC',
-            fields=[
-            ],
+            name="ActionTC",
+            fields=[],
             options={
-                'proxy': True,
+                "proxy": True,
             },
-            bases=('vertimus.action',),
+            bases=("vertimus.action",),
         ),
         migrations.CreateModel(
-            name='ActionTR',
-            fields=[
-            ],
+            name="ActionTR",
+            fields=[],
             options={
-                'proxy': True,
+                "proxy": True,
             },
-            bases=('vertimus.action',),
+            bases=("vertimus.action",),
         ),
         migrations.CreateModel(
-            name='ActionUNDO',
-            fields=[
-            ],
+            name="ActionUNDO",
+            fields=[],
             options={
-                'proxy': True,
+                "proxy": True,
             },
-            bases=('vertimus.action',),
+            bases=("vertimus.action",),
         ),
         migrations.CreateModel(
-            name='ActionUP',
-            fields=[
-            ],
+            name="ActionUP",
+            fields=[],
             options={
-                'proxy': True,
+                "proxy": True,
             },
-            bases=('vertimus.action',),
+            bases=("vertimus.action",),
         ),
         migrations.CreateModel(
-            name='ActionUT',
-            fields=[
-            ],
+            name="ActionUT",
+            fields=[],
             options={
-                'proxy': True,
+                "proxy": True,
             },
-            bases=('vertimus.action',),
+            bases=("vertimus.action",),
         ),
         migrations.CreateModel(
-            name='ActionWC',
-            fields=[
-            ],
+            name="ActionWC",
+            fields=[],
             options={
-                'proxy': True,
+                "proxy": True,
             },
-            bases=('vertimus.action',),
+            bases=("vertimus.action",),
         ),
         migrations.CreateModel(
-            name='StateCommitted',
-            fields=[
-            ],
+            name="StateCommitted",
+            fields=[],
             options={
-                'proxy': True,
+                "proxy": True,
             },
-            bases=('vertimus.state',),
+            bases=("vertimus.state",),
         ),
         migrations.CreateModel(
-            name='StateCommitting',
-            fields=[
-            ],
+            name="StateCommitting",
+            fields=[],
             options={
-                'proxy': True,
+                "proxy": True,
             },
-            bases=('vertimus.state',),
+            bases=("vertimus.state",),
         ),
         migrations.CreateModel(
-            name='StateNone',
-            fields=[
-            ],
+            name="StateNone",
+            fields=[],
             options={
-                'proxy': True,
+                "proxy": True,
             },
-            bases=('vertimus.state',),
+            bases=("vertimus.state",),
         ),
         migrations.CreateModel(
-            name='StateProofread',
-            fields=[
-            ],
+            name="StateProofread",
+            fields=[],
             options={
-                'proxy': True,
+                "proxy": True,
             },
-            bases=('vertimus.state',),
+            bases=("vertimus.state",),
         ),
         migrations.CreateModel(
-            name='StateProofreading',
-            fields=[
-            ],
+            name="StateProofreading",
+            fields=[],
             options={
-                'proxy': True,
+                "proxy": True,
             },
-            bases=('vertimus.state',),
+            bases=("vertimus.state",),
         ),
         migrations.CreateModel(
-            name='StateToCommit',
-            fields=[
-            ],
+            name="StateToCommit",
+            fields=[],
             options={
-                'proxy': True,
+                "proxy": True,
             },
-            bases=('vertimus.state',),
+            bases=("vertimus.state",),
         ),
         migrations.CreateModel(
-            name='StateToReview',
-            fields=[
-            ],
+            name="StateToReview",
+            fields=[],
             options={
-                'proxy': True,
+                "proxy": True,
             },
-            bases=('vertimus.state',),
+            bases=("vertimus.state",),
         ),
         migrations.CreateModel(
-            name='StateTranslated',
-            fields=[
-            ],
+            name="StateTranslated",
+            fields=[],
             options={
-                'proxy': True,
+                "proxy": True,
             },
-            bases=('vertimus.state',),
+            bases=("vertimus.state",),
         ),
         migrations.CreateModel(
-            name='StateTranslating',
-            fields=[
-            ],
+            name="StateTranslating",
+            fields=[],
             options={
-                'proxy': True,
+                "proxy": True,
             },
-            bases=('vertimus.state',),
+            bases=("vertimus.state",),
         ),
         migrations.AlterUniqueTogether(
-            name='state',
-            unique_together=set([('branch', 'domain', 'language')]),
+            name="state",
+            unique_together=set([("branch", "domain", "language")]),
         ),
     ]
diff --git a/vertimus/migrations/0002_state_person_blank.py b/vertimus/migrations/0002_state_person_blank.py
index deb3b327..a3cae18e 100644
--- a/vertimus/migrations/0002_state_person_blank.py
+++ b/vertimus/migrations/0002_state_person_blank.py
@@ -4,13 +4,13 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('vertimus', '0001_initial'),
+        ("vertimus", "0001_initial"),
     ]
 
     operations = [
         migrations.AlterField(
-            model_name='state',
-            name='person',
-            field=models.ForeignKey(blank=True, to='people.Person', null=True, on_delete=models.SET_NULL),
+            model_name="state",
+            name="person",
+            field=models.ForeignKey(blank=True, to="people.Person", null=True, on_delete=models.SET_NULL),
         ),
     ]
diff --git a/vertimus/migrations/0003_add_action_sent_to_ml.py 
b/vertimus/migrations/0003_add_action_sent_to_ml.py
index e1bccf92..9fabed6a 100644
--- a/vertimus/migrations/0003_add_action_sent_to_ml.py
+++ b/vertimus/migrations/0003_add_action_sent_to_ml.py
@@ -4,18 +4,18 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('vertimus', '0002_state_person_blank'),
+        ("vertimus", "0002_state_person_blank"),
     ]
 
     operations = [
         migrations.AddField(
-            model_name='action',
-            name='sent_to_ml',
+            model_name="action",
+            name="sent_to_ml",
             field=models.BooleanField(default=False),
         ),
         migrations.AddField(
-            model_name='actionarchived',
-            name='sent_to_ml',
+            model_name="actionarchived",
+            name="sent_to_ml",
             field=models.BooleanField(default=False),
         ),
     ]
diff --git a/vertimus/migrations/0004_tables_to_utf8mb4.py b/vertimus/migrations/0004_tables_to_utf8mb4.py
index 68882e4c..5300e436 100644
--- a/vertimus/migrations/0004_tables_to_utf8mb4.py
+++ b/vertimus/migrations/0004_tables_to_utf8mb4.py
@@ -2,7 +2,7 @@ from django.db import migrations
 
 
 def to_utf8mb4(apps, schema_editor):
-    if not schema_editor.connection.vendor == 'mysql':
+    if not schema_editor.connection.vendor == "mysql":
         return
     tables = [
         apps.get_model("vertimus", "Action")._meta.db_table,
@@ -11,15 +11,14 @@ def to_utf8mb4(apps, schema_editor):
     ]
     for table_name in tables:
         schema_editor.execute(
-            'ALTER TABLE %s CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci' % table_name,
-            params=None
+            "ALTER TABLE %s CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci" % table_name, 
params=None
         )
 
 
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('vertimus', '0003_add_action_sent_to_ml'),
+        ("vertimus", "0003_add_action_sent_to_ml"),
     ]
 
     operations = [migrations.RunPython(to_utf8mb4, migrations.RunPython.noop, atomic=False)]
diff --git a/vertimus/migrations/0005_action_proxy_pofile.py b/vertimus/migrations/0005_action_proxy_pofile.py
index 6b7f93b6..42401033 100644
--- a/vertimus/migrations/0005_action_proxy_pofile.py
+++ b/vertimus/migrations/0005_action_proxy_pofile.py
@@ -1,38 +1,37 @@
-from django.db import migrations, models
 import django.db.models.deletion
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('stats', '0001_initial'),
-        ('vertimus', '0004_tables_to_utf8mb4'),
+        ("stats", "0001_initial"),
+        ("vertimus", "0004_tables_to_utf8mb4"),
     ]
 
     operations = [
         migrations.CreateModel(
-            name='MergedPoFile',
-            fields=[
-            ],
+            name="MergedPoFile",
+            fields=[],
             options={
-                'proxy': True,
-                'indexes': [],
-                'constraints': [],
+                "proxy": True,
+                "indexes": [],
+                "constraints": [],
             },
-            bases=('stats.pofile',),
+            bases=("stats.pofile",),
         ),
         migrations.AlterField(
-            model_name='action',
-            name='merged_file',
+            model_name="action",
+            name="merged_file",
             field=models.OneToOneField(
-                blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, 
to='vertimus.MergedPoFile'
+                blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, 
to="vertimus.MergedPoFile"
             ),
         ),
         migrations.AlterField(
-            model_name='actionarchived',
-            name='merged_file',
+            model_name="actionarchived",
+            name="merged_file",
             field=models.OneToOneField(
-                blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, 
to='vertimus.MergedPoFile'
+                blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, 
to="vertimus.MergedPoFile"
             ),
         ),
     ]
diff --git a/vertimus/migrations/0006_pofile_path_relative.py 
b/vertimus/migrations/0006_pofile_path_relative.py
index 0818cb8c..59039949 100644
--- a/vertimus/migrations/0006_pofile_path_relative.py
+++ b/vertimus/migrations/0006_pofile_path_relative.py
@@ -1,4 +1,5 @@
 import os
+
 from django.conf import settings
 from django.db import migrations
 
@@ -10,7 +11,7 @@ def strip_path_prefix(apps, schema_editor):
 
     def strip_path(action):
         old_path = action.merged_file.path
-        action.merged_file.path = old_path.split('/' + media_dir + '/')[1]
+        action.merged_file.path = old_path.split("/" + media_dir + "/")[1]
         action.merged_file.save()
 
     for act in action.objects.filter(merged_file__isnull=False):
@@ -22,7 +23,7 @@ def strip_path_prefix(apps, schema_editor):
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('vertimus', '0005_action_proxy_pofile'),
+        ("vertimus", "0005_action_proxy_pofile"),
     ]
 
     operations = [migrations.RunPython(strip_path_prefix, migrations.RunPython.noop)]
diff --git a/vertimus/migrations/0008_actions_on_delete_setnull.py 
b/vertimus/migrations/0008_actions_on_delete_setnull.py
index 63815923..facd1171 100644
--- a/vertimus/migrations/0008_actions_on_delete_setnull.py
+++ b/vertimus/migrations/0008_actions_on_delete_setnull.py
@@ -4,19 +4,19 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('people', '__first__'),
-        ('vertimus', '0006_pofile_path_relative'),
+        ("people", "__first__"),
+        ("vertimus", "0006_pofile_path_relative"),
     ]
 
     operations = [
         migrations.AlterField(
-            model_name='action',
-            name='person',
-            field=models.ForeignKey(null=True, on_delete=models.deletion.SET_NULL, to='people.person'),
+            model_name="action",
+            name="person",
+            field=models.ForeignKey(null=True, on_delete=models.deletion.SET_NULL, to="people.person"),
         ),
         migrations.AlterField(
-            model_name='actionarchived',
-            name='person',
-            field=models.ForeignKey(null=True, on_delete=models.deletion.SET_NULL, to='people.person'),
+            model_name="actionarchived",
+            name="person",
+            field=models.ForeignKey(null=True, on_delete=models.deletion.SET_NULL, to="people.person"),
         ),
     ]
diff --git a/vertimus/models.py b/vertimus/models.py
index a10ae7c4..7b93547e 100644
--- a/vertimus/models.py
+++ b/vertimus/models.py
@@ -10,14 +10,14 @@ from django.db.models.signals import post_save, pre_delete
 from django.dispatch import receiver
 from django.urls import reverse
 from django.utils import timezone
-from django.utils.translation import (
-    override, gettext, gettext_noop, gettext_lazy as _, to_language
-)
+from django.utils.translation import gettext
+from django.utils.translation import gettext_lazy as _
+from django.utils.translation import gettext_noop, override, to_language
 
 from common.utils import run_shell_command, send_mail
 from languages.models import Language
 from people.models import Person
-from stats.models import Branch, Domain, Statistics, PoFile, UnableToCommit
+from stats.models import Branch, Domain, PoFile, Statistics, UnableToCommit
 from stats.signals import pot_has_changed
 from stats.utils import is_po_reduced, po_grep
 from teams.models import Role
@@ -32,43 +32,47 @@ class SendMailFailed(Exception):
 #
 class State(models.Model):
     """State of a module translation"""
+
     branch = models.ForeignKey(Branch, on_delete=models.CASCADE)
     domain = models.ForeignKey(Domain, on_delete=models.CASCADE)
     language = models.ForeignKey(Language, on_delete=models.CASCADE)
     person = models.ForeignKey(Person, blank=True, null=True, on_delete=models.SET_NULL)
 
-    name = models.SlugField(max_length=20, default='None')
+    name = models.SlugField(max_length=20, default="None")
     updated = models.DateTimeField(auto_now=True, editable=False)
 
     class Meta:
-        db_table = 'state'
-        verbose_name = 'state'
-        unique_together = ('branch', 'domain', 'language')
+        db_table = "state"
+        verbose_name = "state"
+        unique_together = ("branch", "domain", "language")
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
-        if self.name == 'None' and getattr(self.__class__, '_name', 'None') != 'None':
+        if self.name == "None" and getattr(self.__class__, "_name", "None") != "None":
             self.name = self.__class__._name
         self.__class__ = {
-            'None': StateNone,
-            'Translating': StateTranslating,
-            'Translated': StateTranslated,
-            'Proofreading': StateProofreading,
-            'Proofread': StateProofread,
-            'ToReview': StateToReview,
-            'ToCommit': StateToCommit,
-            'Committing': StateCommitting,
-            'Committed': StateCommitted,
+            "None": StateNone,
+            "Translating": StateTranslating,
+            "Translated": StateTranslated,
+            "Proofreading": StateProofreading,
+            "Proofread": StateProofread,
+            "ToReview": StateToReview,
+            "ToCommit": StateToCommit,
+            "Committing": StateCommitting,
+            "Committed": StateCommitted,
         }.get(self.name, State)
 
     def __str__(self):
         return "%s: %s %s (%s - %s)" % (
-            self.name, self.branch.module.name,
-            self.branch.name, self.language.name, self.domain.name
+            self.name,
+            self.branch.module.name,
+            self.branch.name,
+            self.language.name,
+            self.domain.name,
         )
 
     def get_absolute_url(self):
-        return reverse('vertimus_by_ids', args=[self.branch_id, self.domain_id, self.language_id])
+        return reverse("vertimus_by_ids", args=[self.branch_id, self.domain_id, self.language_id])
 
     @property
     def stats(self):
@@ -97,26 +101,28 @@ class State(models.Model):
                 return [ActionAA()]
             return []
 
-        action_names.append('WC')
+        action_names.append("WC")
         # Allow the coordinator to cancel current reserved state
-        if person.is_coordinator(self.language.team) and \
-                self.name.endswith('ing') and 'UNDO' not in action_names:
-            action_names.append('UNDO')
-        if person.is_committer(self.language.team) and 'IC' not in action_names:
-            action_names.extend(('Separator', 'IC', 'AA'))
-        return [eval('Action' + action_name)() for action_name in action_names]  # FIXME: eval is unsafe
+        if person.is_coordinator(self.language.team) and self.name.endswith("ing") and "UNDO" not in 
action_names:
+            action_names.append("UNDO")
+        if person.is_committer(self.language.team) and "IC" not in action_names:
+            action_names.extend(("Separator", "IC", "AA"))
+        return [eval("Action" + action_name)() for action_name in action_names]  # FIXME: eval is unsafe
 
     def get_action_sequence_from_level(self, level):
         """Get the sequence corresponding to the requested level.
-           The first level is 1."""
+        The first level is 1."""
         assert level > 0, "Level must be greater than 0"
 
-        query = ActionArchived.objects.filter(
-            state_db=self
-        ).values('sequence').distinct().order_by('-sequence')[level - 1:level]
+        query = (
+            ActionArchived.objects.filter(state_db=self)
+            .values("sequence")
+            .distinct()
+            .order_by("-sequence")[level - 1 : level]
+        )
         sequence = None
         if len(query) > 0:
-            sequence = query[0]['sequence']
+            sequence = query[0]["sequence"]
         return sequence
 
     def involved_persons(self, extra_user=None):
@@ -129,14 +135,14 @@ class State(models.Model):
 
     def get_latest_po_file_action(self):
         try:
-            return Action.objects.filter(file__endswith=".po", state_db=self).latest('id')
+            return Action.objects.filter(file__endswith=".po", state_db=self).latest("id")
         except Action.DoesNotExist:
             return None
 
 
 class StateNone(State):
-    _name = 'None'
-    description = _('Inactive')
+    _name = "None"
+    description = _("Inactive")
 
     class Meta:
         proxy = True
@@ -144,18 +150,17 @@ class StateNone(State):
     def get_available_actions(self, person):
         action_names = []
 
-        if (
-            (self.language.team and person.is_translator(self.language.team))
-            or person.is_maintainer_of(self.branch.module)
+        if (self.language.team and person.is_translator(self.language.team)) or person.is_maintainer_of(
+            self.branch.module
         ):
-            action_names = ['RT', 'UT']
+            action_names = ["RT", "UT"]
 
         return self._get_available_actions(person, action_names)
 
 
 class StateTranslating(State):
-    _name = 'Translating'
-    description = _('Translating')
+    _name = "Translating"
+    description = _("Translating")
 
     class Meta:
         proxy = True
@@ -163,15 +168,15 @@ class StateTranslating(State):
     def get_available_actions(self, person):
         action_names = []
 
-        if (self.person == person):
-            action_names = ['UT', 'UNDO']
+        if self.person == person:
+            action_names = ["UT", "UNDO"]
 
         return self._get_available_actions(person, action_names)
 
 
 class StateTranslated(State):
-    _name = 'Translated'
-    description = _('Translated')
+    _name = "Translated"
+    description = _("Translated")
 
     class Meta:
         proxy = True
@@ -180,24 +185,24 @@ class StateTranslated(State):
         action_names = []
 
         if person.is_reviewer(self.language.team):
-            action_names.append('RP')
-            action_names.append('UP')
-            action_names.append('TR')
+            action_names.append("RP")
+            action_names.append("UP")
+            action_names.append("TR")
 
         if person.is_translator(self.language.team):
-            action_names.append('RT')
+            action_names.append("RT")
 
         if person.is_committer(self.language.team):
-            action_names.append('TC')
+            action_names.append("TC")
             if self.able_to_commit():
-                action_names.append('CI')
+                action_names.append("CI")
 
         return self._get_available_actions(person, action_names)
 
 
 class StateProofreading(State):
-    _name = 'Proofreading'
-    description = _('Proofreading')
+    _name = "Proofreading"
+    description = _("Proofreading")
 
     class Meta:
         proxy = True
@@ -206,36 +211,36 @@ class StateProofreading(State):
         action_names = []
 
         if person.is_reviewer(self.language.team):
-            if (self.person == person):
-                action_names = ['UP', 'TR', 'TC', 'UNDO']
+            if self.person == person:
+                action_names = ["UP", "TR", "TC", "UNDO"]
 
         return self._get_available_actions(person, action_names)
 
 
 class StateProofread(State):
-    _name = 'Proofread'
+    _name = "Proofread"
     # Translators: This is a status, not a verb
-    description = _('Proofread')
+    description = _("Proofread")
 
     class Meta:
         proxy = True
 
     def get_available_actions(self, person):
         if person.is_reviewer(self.language.team):
-            action_names = ['TC', 'RP', 'TR']
+            action_names = ["TC", "RP", "TR"]
         else:
             action_names = []
         if person.is_committer(self.language.team):
-            action_names.append('RC')
+            action_names.append("RC")
             if self.able_to_commit():
-                action_names.insert(1, 'CI')
+                action_names.insert(1, "CI")
 
         return self._get_available_actions(person, action_names)
 
 
 class StateToReview(State):
-    _name = 'ToReview'
-    description = _('To Review')
+    _name = "ToReview"
+    description = _("To Review")
 
     class Meta:
         proxy = True
@@ -243,23 +248,23 @@ class StateToReview(State):
     def get_available_actions(self, person):
         action_names = []
         if person.is_translator(self.language.team):
-            action_names.append('RT')
+            action_names.append("RT")
 
         return self._get_available_actions(person, action_names)
 
 
 class StateToCommit(State):
-    _name = 'ToCommit'
-    description = _('To Commit')
+    _name = "ToCommit"
+    description = _("To Commit")
 
     class Meta:
         proxy = True
 
     def get_available_actions(self, person):
         if person.is_committer(self.language.team):
-            action_names = ['RC', 'TR', 'UNDO']
+            action_names = ["RC", "TR", "UNDO"]
             if self.able_to_commit():
-                action_names.insert(1, 'CI')
+                action_names.insert(1, "CI")
         else:
             action_names = []
 
@@ -267,8 +272,8 @@ class StateToCommit(State):
 
 
 class StateCommitting(State):
-    _name = 'Committing'
-    description = _('Committing')
+    _name = "Committing"
+    description = _("Committing")
 
     class Meta:
         proxy = True
@@ -277,24 +282,24 @@ class StateCommitting(State):
         action_names = []
 
         if person.is_committer(self.language.team):
-            if (self.person == person):
-                action_names = ['IC', 'TR', 'UNDO']
+            if self.person == person:
+                action_names = ["IC", "TR", "UNDO"]
             if self.able_to_commit():
-                action_names.insert(0, 'CI')
+                action_names.insert(0, "CI")
 
         return self._get_available_actions(person, action_names)
 
 
 class StateCommitted(State):
-    _name = 'Committed'
-    description = _('Committed')
+    _name = "Committed"
+    description = _("Committed")
 
     class Meta:
         proxy = True
 
     def get_available_actions(self, person):
         if person.is_committer(self.language.team):
-            action_names = ['AA']
+            action_names = ["AA"]
         else:
             action_names = []
 
@@ -306,22 +311,22 @@ class StateCommitted(State):
 #
 
 ACTION_NAMES = (
-    ('WC', _('Write a comment')),
-    ('RT', _('Reserve for translation')),
-    ('UT', _('Upload the new translation')),
-    ('RP', _('Reserve for proofreading')),
-    ('UP', _('Upload the proofread translation')),
+    ("WC", _("Write a comment")),
+    ("RT", _("Reserve for translation")),
+    ("UT", _("Upload the new translation")),
+    ("RP", _("Reserve for proofreading")),
+    ("UP", _("Upload the proofread translation")),
     # Translators: this means the file is ready to be committed in repository
-    ('TC', _('Ready for submission')),
-    ('CI', _('Submit to repository')),
+    ("TC", _("Ready for submission")),
+    ("CI", _("Submit to repository")),
     # Translators: this indicates a committer is going to commit the file in the repository
-    ('RC', _('Reserve to submit')),
+    ("RC", _("Reserve to submit")),
     # Translators: this is used to indicate the file has been committed in the repository
-    ('IC', _('Inform of submission')),
+    ("IC", _("Inform of submission")),
     # Translators: regardless of the translation completion, this file need to be reviewed
-    ('TR', _('Rework needed')),
-    ('AA', _('Archive the actions')),
-    ('UNDO', _('Undo the last state change')),
+    ("TR", _("Rework needed")),
+    ("AA", _("Archive the actions")),
+    ("UNDO", _("Undo the last state change")),
 )
 
 
@@ -339,7 +344,8 @@ def generate_upload_filename(instance, filename):
         instance.state_db.domain.name,
         instance.state_db.language.locale,
         instance.state_db.id,
-        ext)
+        ext,
+    )
     return "%s/%s" % (settings.UPLOAD_DIR, new_filename)
 
 
@@ -353,7 +359,8 @@ class MergedPoFile(PoFile):
 
 
 class ActionAbstract(models.Model):
-    """ Common model for Action and ActionArchived """
+    """Common model for Action and ActionArchived"""
+
     state_db = models.ForeignKey(State, on_delete=models.CASCADE)
     person = models.ForeignKey(Person, null=True, on_delete=models.SET_NULL)
 
@@ -385,7 +392,7 @@ class ActionAbstract(models.Model):
 
     @property
     def person_name(self):
-        return self.person.name if self.person else gettext('deleted account')
+        return self.person.name if self.person else gettext("deleted account")
 
     @property
     def most_uptodate_file(self):
@@ -397,15 +404,12 @@ class ActionAbstract(models.Model):
 
     @property
     def can_build(self):
-        return (
-            not isinstance(self, ActionArchived)
-            and self.state_db.domain.can_build_docs(self.state_db.branch)
-        )
+        return not isinstance(self, ActionArchived) and 
self.state_db.domain.can_build_docs(self.state_db.branch)
 
     @property
     def build_url(self):
-        path = settings.SCRATCHDIR / 'HTML' / str(self.pk) / 'index.html'
-        return '/' + str(path.relative_to(settings.SCRATCHDIR)) if path.exists() else None
+        path = settings.SCRATCHDIR / "HTML" / str(self.pk) / "index.html"
+        return "/" + str(path.relative_to(settings.SCRATCHDIR)) if path.exists() else None
 
     def get_filename(self):
         if self.file:
@@ -425,21 +429,23 @@ class ActionAbstract(models.Model):
         """
         history = []
         if state or sequence:
-            file_history = [{'action_id': 0, 'title': gettext("File in repository")}]
+            file_history = [{"action_id": 0, "title": gettext("File in repository")}]
             if not sequence:
                 query = cls.objects.filter(state_db__id=state.id)
             else:
                 # Not necessary to filter on state with a sequence (unique)
                 query = cls.objects.filter(sequence=sequence)
-            for action in query.order_by('id'):
+            for action in query.order_by("id"):
                 history.append((action, list(file_history)))
-                if action.file and action.file.path.endswith('.po'):
-                    file_history.insert(0, {
-                        'action_id': action.id,
-                        'title': gettext("Uploaded file by %(name)s on %(date)s") % {
-                            'name': action.person_name,
-                            'date': action.created},
-                    })
+                if action.file and action.file.path.endswith(".po"):
+                    file_history.insert(
+                        0,
+                        {
+                            "action_id": action.id,
+                            "title": gettext("Uploaded file by %(name)s on %(date)s")
+                            % {"name": action.person_name, "date": action.created},
+                        },
+                    )
         return history
 
 
@@ -449,20 +455,20 @@ class Action(ActionAbstract):
     )
 
     class Meta:
-        db_table = 'action'
-        verbose_name = 'action'
+        db_table = "action"
+        verbose_name = "action"
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         if self.name:
-            self.__class__ = eval('Action' + self.name)
+            self.__class__ = eval("Action" + self.name)
         else:
-            if getattr(self.__class__, 'name'):
+            if getattr(self.__class__, "name"):
                 self.name = self.__class__.name
 
     @classmethod
     def new_by_name(cls, action_name, **kwargs):
-        return eval('Action' + action_name)(**kwargs)  # FIXME: eval is unsafe
+        return eval("Action" + action_name)(**kwargs)  # FIXME: eval is unsafe
 
     def save(self, *args, **kwargs):
         if not self.id and not self.created:
@@ -479,29 +485,29 @@ class Action(ActionAbstract):
 
         if self.target_state == StateCommitted:
             # Committed is the last state of the workflow, archive actions
-            arch_action = self.new_by_name('AA', person=self.person)
+            arch_action = self.new_by_name("AA", person=self.person)
             arch_action.apply_on(self.state_db, {})
 
     def apply_on(self, state, form_data):
         if self.name not in [a.name for a in state.get_available_actions(self.person)]:
-            raise Exception('Action not allowed')
+            raise Exception("Action not allowed")
         if state.pk is None:
             state.save()
         self.state_db = state
         if self.file:
             self.file.save(os.path.basename(self.file.name), self.file, save=False)
-        if form_data.get('comment'):
-            self.comment = form_data['comment']
-        self.sent_to_ml = form_data.get('send_to_ml', False)
+        if form_data.get("comment"):
+            self.comment = form_data["comment"]
+        self.sent_to_ml = form_data.get("send_to_ml", False)
         self.save()
         self.update_state()  # Do not save action after that, they have been archived
 
-        if form_data.get('send_to_ml'):
+        if form_data.get("send_to_ml"):
             self.send_mail_new_state(state, (state.language.team.mailing_list,))
         elif self.send_mail_to_ml or self.comment:
             # If the action is normally sent to ML (but unchecked) or if the form
             # contains a comment, send message to state participants
-            recipients = set(state.involved_persons().exclude(pk=self.person.pk).values_list('email', 
flat=True))
+            recipients = set(state.involved_persons().exclude(pk=self.person.pk).values_list("email", 
flat=True))
             if self.comment and not recipients:
                 # In any case, the coordinators should be aware of comments
                 recipients = [pers.email for pers in state.language.team.get_coordinators() if pers.email]
@@ -516,7 +522,7 @@ class Action(ActionAbstract):
         if self.id:
             prev_actions_with_po = prev_actions_with_po.filter(id__lt=self.id)
         try:
-            return prev_actions_with_po.latest('id')
+            return prev_actions_with_po.latest("id")
         except Action.DoesNotExist:
             return None
 
@@ -526,13 +532,11 @@ class Action(ActionAbstract):
             return
         if not self.merged_file:
             merged_path = "%s.merged.po" % self.file.path[:-3]
-            self.merged_file = MergedPoFile.objects.create(
-                path=os.path.relpath(merged_path, settings.MEDIA_ROOT)
-            )
+            self.merged_file = MergedPoFile.objects.create(path=os.path.relpath(merged_path, 
settings.MEDIA_ROOT))
             self.save()
             return  # post_save will call merge_file_with_pot again
         merged_path = self.merged_file.full_path
-        command = ['msgmerge', '--previous', '-o', merged_path, self.file.path, str(pot_file)]
+        command = ["msgmerge", "--previous", "-o", merged_path, self.file.path, str(pot_file)]
         run_shell_command(command)
         # If uploaded file is reduced, run po_grep *after* merge
         if is_po_reduced(self.file.path):
@@ -553,36 +557,29 @@ class Action(ActionAbstract):
             return
 
         url = "https://%s%s"; % (
-            settings.SITE_DOMAIN, reverse(
-                'vertimus_by_names',
-                args=(
-                    state.branch.module.name,
-                    state.branch.name,
-                    state.domain.name,
-                    state.language.locale
-                )
-            )
+            settings.SITE_DOMAIN,
+            reverse(
+                "vertimus_by_names",
+                args=(state.branch.module.name, state.branch.name, state.domain.name, state.language.locale),
+            ),
         )
-        subject = state.branch.module.name + ' - ' + state.branch.name
+        subject = state.branch.module.name + " - " + state.branch.name
         # to_language may be unnecessary after https://code.djangoproject.com/ticket/32581 is solved
         with override(to_language(Language.django_locale(state.language.locale))):
             message = gettext("Hello,") + "\n\n" + gettext(self.default_message) + "\n%(url)s\n\n"
             message = message % {
-                'module': state.branch.module.name,
-                'branch': state.branch.name,
-                'domain': state.domain.name,
-                'language': state.language.get_name(),
-                'new_state': state.description,
-                'url': url
+                "module": state.branch.module.name,
+                "branch": state.branch.name,
+                "domain": state.domain.name,
+                "language": state.language.get_name(),
+                "new_state": state.description,
+                "url": url,
             }
             message += self.comment or gettext("Without comment")
             message += "\n\n" + self.person.name
             message += "\n--\n" + gettext("This is an automated message sent from %s.") % 
settings.SITE_DOMAIN
         try:
-            send_mail(
-                subject, message, to=recipient_list,
-                headers={settings.EMAIL_HEADER_NAME: state.description}
-            )
+            send_mail(subject, message, to=recipient_list, headers={settings.EMAIL_HEADER_NAME: 
state.description})
         except Exception as exc:
             raise SendMailFailed("Sending message failed: %r" % exc)
 
@@ -597,26 +594,24 @@ class ActionArchived(ActionAbstract):
     sequence = models.IntegerField(null=True)
 
     class Meta:
-        db_table = 'action_archived'
+        db_table = "action_archived"
 
     @classmethod
     def clean_old_actions(cls, days):
-        """ Delete old archived actions after some (now-days) time """
+        """Delete old archived actions after some (now-days) time"""
         # In each sequence, test date of the latest action, to delete whole sequences instead of individual 
actions
-        for action in ActionArchived.objects.values(
-            'sequence'
-        ).annotate(
-            max_created=Max('created')
-        ).filter(
-            max_created__lt=timezone.now() - timedelta(days=days)
+        for action in (
+            ActionArchived.objects.values("sequence")
+            .annotate(max_created=Max("created"))
+            .filter(max_created__lt=timezone.now() - timedelta(days=days))
         ):
             # Call each action delete() so as file is also deleted
-            for act in ActionArchived.objects.filter(sequence=action['sequence']):
+            for act in ActionArchived.objects.filter(sequence=action["sequence"]):
                 act.delete()
 
 
 class ActionWC(Action):
-    name = 'WC'
+    name = "WC"
     comment_is_required = True
     default_message = gettext_noop(
         "A new comment has been posted on %(module)s — %(branch)s — %(domain)s (%(language)s)."
@@ -627,7 +622,7 @@ class ActionWC(Action):
 
 
 class ActionRT(Action):
-    name = 'RT'
+    name = "RT"
     target_state = StateTranslating
     file_is_prohibited = True
 
@@ -636,7 +631,7 @@ class ActionRT(Action):
 
 
 class ActionUT(Action):
-    name = 'UT'
+    name = "UT"
     target_state = StateTranslated
     file_is_required = True
     send_mail_to_ml = True
@@ -646,7 +641,7 @@ class ActionUT(Action):
 
 
 class ActionRP(Action):
-    name = 'RP'
+    name = "RP"
     target_state = StateProofreading
     file_is_prohibited = True
 
@@ -655,7 +650,7 @@ class ActionRP(Action):
 
 
 class ActionUP(Action):
-    name = 'UP'
+    name = "UP"
     target_state = StateProofread
     file_is_required = True
     send_mail_to_ml = True
@@ -665,7 +660,7 @@ class ActionUP(Action):
 
 
 class ActionTC(Action):
-    name = 'TC'
+    name = "TC"
     target_state = StateToCommit
 
     class Meta:
@@ -679,7 +674,7 @@ class ActionTC(Action):
 
 
 class ActionCI(Action):
-    name = 'CI'
+    name = "CI"
     target_state = StateCommitted
     file_is_prohibited = True
     send_mail_to_ml = True
@@ -690,33 +685,32 @@ class ActionCI(Action):
     def apply_on(self, state, form_data):
         self.state_db = state
         action_with_po = self.get_previous_action_with_po()
-        author = form_data['author'].as_author() if form_data['author'] else None
+        author = form_data["author"].as_author() if form_data["author"] else None
         try:
-            commit_hash = state.branch.commit_po(
-                action_with_po.file.path, state.domain, state.language, author)
+            commit_hash = state.branch.commit_po(action_with_po.file.path, state.domain, state.language, 
author)
         except Exception:
             # Commit failed, state unchanged
             raise Exception(_("The commit failed. The error was: “%s”") % sys.exc_info()[1])
 
         msg = gettext("The file has been successfully committed to the repository.")
-        if form_data.get('sync_master', False) and not state.branch.is_head():
+        if form_data.get("sync_master", False) and not state.branch.is_head():
             # Cherry-pick the commit on the master branch
             main_branch = state.branch.module.get_head_branch()
             success = main_branch.cherrypick_commit(commit_hash, state.domain)
             if success:
                 msg += gettext(" Additionally, the synchronization with the %(name)s branch succeeded.") % {
-                    'name': main_branch.name
+                    "name": main_branch.name
                 }
             else:
                 msg += gettext(" However, the synchronization with the %(name)s branch failed.") % {
-                    'name': main_branch.name
+                    "name": main_branch.name
                 }
         super().apply_on(state, form_data)  # Mail sent in super
         return msg
 
 
 class ActionRC(Action):
-    name = 'RC'
+    name = "RC"
     target_state = StateCommitting
     file_is_prohibited = True
 
@@ -725,7 +719,7 @@ class ActionRC(Action):
 
 
 class ActionIC(Action):
-    name = 'IC'
+    name = "IC"
     target_state = StateCommitted
     send_mail_to_ml = True
 
@@ -734,7 +728,7 @@ class ActionIC(Action):
 
 
 class ActionTR(Action):
-    name = 'TR'
+    name = "TR"
     target_state = StateToReview
     arg_is_required = True
     send_mail_to_ml = True
@@ -744,7 +738,7 @@ class ActionTR(Action):
 
 
 class ActionAA(Action):
-    name = 'AA'
+    name = "AA"
     target_state = StateNone
 
     class Meta:
@@ -752,7 +746,7 @@ class ActionAA(Action):
 
     def apply_on(self, state, form_data):
         super().apply_on(state, form_data)
-        all_actions = Action.objects.filter(state_db=state).order_by('id').all()
+        all_actions = Action.objects.filter(state_db=state).order_by("id").all()
 
         sequence = None
         for action in all_actions:
@@ -768,7 +762,8 @@ class ActionAA(Action):
                 name=action.name,
                 created=action.created,
                 comment=action.comment,
-                file=file_to_archive)
+                file=file_to_archive,
+            )
             if file_to_archive:
                 action_archived.file.save(os.path.basename(action.file.name), file_to_archive, save=False)
 
@@ -784,20 +779,20 @@ class ActionAA(Action):
 
 
 class ActionUNDO(Action):
-    name = 'UNDO'
+    name = "UNDO"
 
     class Meta:
         proxy = True
 
     def update_state(self):
         # Look for the action to revert, excluding WC because this action is a noop on State
-        actions = Action.objects.filter(state_db__id=self.state_db.id).exclude(name='WC').order_by('-id')
+        actions = Action.objects.filter(state_db__id=self.state_db.id).exclude(name="WC").order_by("-id")
         skip_next = False
         for action in actions:
             if skip_next:
                 skip_next = False
                 continue
-            if action.name == 'UNDO':
+            if action.name == "UNDO":
                 # Skip Undo and the associated action
                 skip_next = True
                 continue
@@ -810,7 +805,8 @@ class ActionUNDO(Action):
 
 
 class ActionSeparator:
-    """ Fake action to add a separator in action menu """
+    """Fake action to add a separator in action menu"""
+
     name = None
     description = "--------"
 
@@ -821,11 +817,11 @@ class ActionSeparator:
 @receiver(pot_has_changed)
 def update_uploaded_files(sender, **kwargs):
     """Callback to handle pot_file_changed signal"""
-    actions = Action.objects.filter(state_db__branch=kwargs['branch'],
-                                    state_db__domain=kwargs['domain'],
-                                    file__endswith=".po")
+    actions = Action.objects.filter(
+        state_db__branch=kwargs["branch"], state_db__domain=kwargs["domain"], file__endswith=".po"
+    )
     for action in actions:
-        action.merge_file_with_pot(kwargs['potfile'])
+        action.merge_file_with_pot(kwargs["potfile"])
 
 
 @receiver(post_save)
@@ -836,7 +832,7 @@ def merge_uploaded_file(sender, instance, **kwargs):
     """
     if not isinstance(instance, Action):
         return
-    if instance.file and instance.file.path.endswith('.po'):
+    if instance.file and instance.file.path.endswith(".po"):
         try:
             stat = Statistics.objects.get(
                 branch=instance.state_db.branch, domain=instance.state_db.domain, language=None
@@ -855,15 +851,15 @@ def delete_action_files(sender, instance, **kwargs):
     - the merged file
     - the html dir where docs are built
     """
-    if not isinstance(instance, ActionAbstract) or not getattr(instance, 'file'):
+    if not isinstance(instance, ActionAbstract) or not getattr(instance, "file"):
         return
-    if instance.file.path.endswith('.po'):
+    if instance.file.path.endswith(".po"):
         if instance.merged_file:
             if os.access(instance.merged_file.full_path, os.W_OK):
                 os.remove(instance.merged_file.full_path)
     if os.access(instance.file.path, os.W_OK):
         os.remove(instance.file.path)
-    html_dir = settings.SCRATCHDIR / 'HTML' / str(instance.pk)
+    html_dir = settings.SCRATCHDIR / "HTML" / str(instance.pk)
     if html_dir.exists():
         shutil.rmtree(str(html_dir))
 
diff --git a/vertimus/templatetags/vertimus.py b/vertimus/templatetags/vertimus.py
index f08619ec..d22b6ca7 100644
--- a/vertimus/templatetags/vertimus.py
+++ b/vertimus/templatetags/vertimus.py
@@ -7,27 +7,34 @@ register = template.Library()
 
 @register.filter
 def as_tr(field):
-    help_html = ''
+    help_html = ""
     if field.help_text:
         help_html = format_html('<br><span class="help">{}</span>', field.help_text)
-    help_link = ''
+    help_link = ""
     # This is a custom attribute possibly set in forms.py
-    if hasattr(field.field, 'help_link'):
+    if hasattr(field.field, "help_link"):
         help_link = format_html(
             '<span class="help_link">'
             '<a href="{}" data-target="#modal-container" role="button" data-toggle="modal">'
             '<img src="{}" alt="help icon">'
-            '</a></span>', field.field.help_link, static('img/help.png')
+            "</a></span>",
+            field.field.help_link,
+            static("img/help.png"),
         )
-    errors_html = ''
+    errors_html = ""
     if field.errors:
-        errors_html = ''.join(["%s" % err for err in field.errors])
+        errors_html = "".join(["%s" % err for err in field.errors])
 
     return format_html(
-        '<tr class="tr_{}"><th>{}</th><td>' +
-        ('<div class="errornote error">{}</div>' if errors_html else '') +
-        '{}{}{}</td></tr>',
-        field.name, field.label_tag(), errors_html, field.as_widget(), help_link, help_html
+        '<tr class="tr_{}"><th>{}</th><td>'
+        + ('<div class="errornote error">{}</div>' if errors_html else "")
+        + "{}{}{}</td></tr>",
+        field.name,
+        field.label_tag(),
+        errors_html,
+        field.as_widget(),
+        help_link,
+        help_html,
     )
 
 
@@ -38,5 +45,8 @@ def as_tr_cb(field):
         label = format_html('{} <span class="help">({})</span>', label, field.help_text)
     return format_html(
         '<tr class="tr_{}"><td></td><td>{} <label for="id_{}">{}</label></td></tr>',
-        field.name, field.as_widget(), field.name, label
+        field.name,
+        field.as_widget(),
+        field.name,
+        label,
     )
diff --git a/vertimus/tests/tests.py b/vertimus/tests/tests.py
index b08fc7ad..512921bf 100644
--- a/vertimus/tests/tests.py
+++ b/vertimus/tests/tests.py
@@ -6,10 +6,10 @@ from unittest.mock import patch
 from xml.dom.minidom import parseString
 
 from django.conf import settings
-from django.core.files.base import File, ContentFile
+from django.core import mail
+from django.core.files.base import ContentFile, File
 from django.core.files.uploadedfile import SimpleUploadedFile
 from django.core.management import call_command
-from django.core import mail
 from django.http import QueryDict
 from django.test import TestCase
 from django.test.utils import override_settings
@@ -18,53 +18,70 @@ from django.utils.datastructures import MultiValueDict
 
 from languages.models import Language
 from people.models import Person
-from teams.models import Role, Team
-from teams.tests import TeamsAndRolesMixin
-from stats.models import Module, Branch, Release, Category, CategoryName, Domain, Statistics
+from stats.models import (
+    Branch,
+    Category,
+    CategoryName,
+    Domain,
+    Module,
+    Release,
+    Statistics,
+)
 from stats.tests.tests import TestModuleBase
 from stats.tests.utils import PatchShellCommand, test_scratchdir
+from teams.models import Role, Team
+from teams.tests import TeamsAndRolesMixin
+from vertimus.forms import ActionForm
 from vertimus.models import (
-    Action, ActionArchived, ActionCI, ActionUNDO, ActionWC, State, StateCommitted,
-    StateCommitting, StateNone, StateProofread, StateProofreading, StateToCommit,
-    StateToReview, StateTranslated, StateTranslating,
+    Action,
+    ActionArchived,
+    ActionCI,
+    ActionUNDO,
+    ActionWC,
+    State,
+    StateCommitted,
+    StateCommitting,
+    StateNone,
+    StateProofread,
+    StateProofreading,
+    StateToCommit,
+    StateToReview,
+    StateTranslated,
+    StateTranslating,
 )
-from vertimus.forms import ActionForm
 
 MEDIA_ROOT = Path(tempfile.mkdtemp())
 
 
 @override_settings(MEDIA_ROOT=MEDIA_ROOT)
 class VertimusTest(TeamsAndRolesMixin, TestCase):
-
     def setUp(self):
         super().setUp()
 
         self.m = Module.objects.create(
-            name='gnome-hello', description='GNOME Hello',
+            name="gnome-hello",
+            description="GNOME Hello",
             bugs_base="https://gitlab.gnome.org/GNOME/gnome-hello/issues";,
-            vcs_type='git', vcs_root="https://gitlab.gnome.org/GNOME/gnome-hello.git";,
-            vcs_web="https://gitlab.gnome.org/GNOME/gnome-hello/";
+            vcs_type="git",
+            vcs_root="https://gitlab.gnome.org/GNOME/gnome-hello.git";,
+            vcs_web="https://gitlab.gnome.org/GNOME/gnome-hello/";,
         )
 
         Branch.checkout_on_creation = False
-        self.b = Branch(name='gnome-2-24', module=self.m)
+        self.b = Branch(name="gnome-2-24", module=self.m)
         # Block the update of Statistics by the thread
         self.b.save(update_statistics=False)
 
         self.r = Release.objects.create(
-            name='gnome-2-24', status='official',
-            description='GNOME 2.24 (stable)',
-            string_frozen=True
+            name="gnome-2-24", status="official", description="GNOME 2.24 (stable)", string_frozen=True
         )
 
         self.c = Category.objects.create(
-            release=self.r, branch=self.b, name=CategoryName.objects.create(name='Desktop')
+            release=self.r, branch=self.b, name=CategoryName.objects.create(name="Desktop")
         )
 
         self.d = Domain.objects.create(
-            module=self.m, name='po',
-            description='UI translations',
-            dtype='ui', layout='po/{lang}.po'
+            module=self.m, name="po", description="UI translations", dtype="ui", layout="po/{lang}.po"
         )
         Statistics.objects.create(language=None, branch=self.b, domain=self.d)
         self.files_to_clean = []
@@ -74,12 +91,12 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
             if os.path.exists(path):
                 os.remove(path)
 
-    def upload_file(self, to_state, action_code='UP', pers=None):
-        """ Test utility to add an uploaded file to a state """
-        test_file = ContentFile('test content')
-        test_file.name = 'mytestfile.po'
+    def upload_file(self, to_state, action_code="UP", pers=None):
+        """Test utility to add an uploaded file to a state"""
+        test_file = ContentFile("test content")
+        test_file.name = "mytestfile.po"
         action = Action.new_by_name(action_code, person=pers or self.pr, file=test_file)
-        action.apply_on(to_state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Done."})
+        action.apply_on(to_state, {"send_to_ml": action.send_mail_to_ml, "comment": "Done."})
         self.files_to_clean.append(action.file.path)
         return action.file
 
@@ -87,157 +104,157 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
         state = StateNone(branch=self.b, domain=self.d, language=self.language)
 
         action_names = [a.name for a in state.get_available_actions(self.pn)]
-        self.assertEqual(action_names, ['WC'])
+        self.assertEqual(action_names, ["WC"])
 
         for p in (self.pt, self.pr):
             action_names = [a.name for a in state.get_available_actions(p)]
-            self.assertEqual(action_names, ['RT', 'UT', 'WC'])
+            self.assertEqual(action_names, ["RT", "UT", "WC"])
 
         for p in (self.pc, self.pcoo):
             action_names = [a.name for a in state.get_available_actions(p)]
-            self.assertEqual(action_names, ['RT', 'UT', 'WC', None, 'IC', 'AA'])
+            self.assertEqual(action_names, ["RT", "UT", "WC", None, "IC", "AA"])
 
     def test_state_translating(self):
         state = StateTranslating(branch=self.b, domain=self.d, language=self.language, person=self.pt)
 
         for p in (self.pn, self.pr):
             action_names = [a.name for a in state.get_available_actions(p)]
-            self.assertEqual(action_names, ['WC'])
+            self.assertEqual(action_names, ["WC"])
 
         action_names = [a.name for a in state.get_available_actions(self.pc)]
-        self.assertEqual(action_names, ['WC', None, 'IC', 'AA'])
+        self.assertEqual(action_names, ["WC", None, "IC", "AA"])
 
         action_names = [a.name for a in state.get_available_actions(self.pcoo)]
-        self.assertEqual(action_names, ['WC', 'UNDO', None, 'IC', 'AA'])
+        self.assertEqual(action_names, ["WC", "UNDO", None, "IC", "AA"])
 
         # Same person
         action_names = [a.name for a in state.get_available_actions(self.pt)]
-        self.assertEqual(action_names, ['UT', 'UNDO', 'WC'])
+        self.assertEqual(action_names, ["UT", "UNDO", "WC"])
 
     def test_state_translated(self):
         state = StateTranslated(branch=self.b, domain=self.d, language=self.language, person=self.pt)
 
         action_names = [a.name for a in state.get_available_actions(self.pn)]
-        self.assertEqual(action_names, ['WC'])
+        self.assertEqual(action_names, ["WC"])
 
         action_names = [a.name for a in state.get_available_actions(self.pt)]
-        self.assertEqual(action_names, ['RT', 'WC'])
+        self.assertEqual(action_names, ["RT", "WC"])
 
         action_names = [a.name for a in state.get_available_actions(self.pr)]
-        self.assertEqual(action_names, ['RP', 'UP', 'TR', 'RT', 'WC'])
+        self.assertEqual(action_names, ["RP", "UP", "TR", "RT", "WC"])
 
         for p in (self.pc, self.pcoo):
             action_names = [a.name for a in state.get_available_actions(p)]
-            self.assertEqual(action_names, ['RP', 'UP', 'TR', 'RT', 'TC', 'WC', None, 'IC', 'AA'])
+            self.assertEqual(action_names, ["RP", "UP", "TR", "RT", "TC", "WC", None, "IC", "AA"])
 
     def test_state_proofreading(self):
         state = StateProofreading(branch=self.b, domain=self.d, language=self.language, person=self.pr)
 
         for p in (self.pn, self.pt):
             action_names = [a.name for a in state.get_available_actions(p)]
-            self.assertEqual(action_names, ['WC'])
+            self.assertEqual(action_names, ["WC"])
 
         action_names = [a.name for a in state.get_available_actions(self.pc)]
-        self.assertEqual(action_names, ['WC', None, 'IC', 'AA'])
+        self.assertEqual(action_names, ["WC", None, "IC", "AA"])
 
         action_names = [a.name for a in state.get_available_actions(self.pcoo)]
-        self.assertEqual(action_names, ['WC', 'UNDO', None, 'IC', 'AA'])
+        self.assertEqual(action_names, ["WC", "UNDO", None, "IC", "AA"])
 
         # Same person and reviewer
         action_names = [a.name for a in state.get_available_actions(self.pr)]
-        self.assertEqual(action_names, ['UP', 'TR', 'TC', 'UNDO', 'WC'])
+        self.assertEqual(action_names, ["UP", "TR", "TC", "UNDO", "WC"])
 
     def test_state_proofread(self):
         state = StateProofread(branch=self.b, domain=self.d, language=self.language, person=self.pr)
 
         for p in (self.pn, self.pt):
             action_names = [a.name for a in state.get_available_actions(p)]
-            self.assertEqual(action_names, ['WC'])
+            self.assertEqual(action_names, ["WC"])
 
         action_names = [a.name for a in state.get_available_actions(self.pr)]
-        self.assertEqual(action_names, ['TC', 'RP', 'TR', 'WC'])
+        self.assertEqual(action_names, ["TC", "RP", "TR", "WC"])
 
         for p in (self.pc, self.pcoo):
             action_names = [a.name for a in state.get_available_actions(p)]
-            self.assertEqual(action_names, ['TC', 'RP', 'TR', 'RC', 'WC', None, 'IC', 'AA'])
+            self.assertEqual(action_names, ["TC", "RP", "TR", "RC", "WC", None, "IC", "AA"])
 
     def test_state_to_review(self):
         state = StateToReview(branch=self.b, domain=self.d, language=self.language, person=self.pt)
 
         action_names = [a.name for a in state.get_available_actions(self.pn)]
-        self.assertEqual(action_names, ['WC'])
+        self.assertEqual(action_names, ["WC"])
 
         for p in (self.pt, self.pr):
             action_names = [a.name for a in state.get_available_actions(p)]
-            self.assertEqual(action_names, ['RT', 'WC'])
+            self.assertEqual(action_names, ["RT", "WC"])
 
         for p in (self.pc, self.pcoo):
             action_names = [a.name for a in state.get_available_actions(p)]
-            self.assertEqual(action_names, ['RT', 'WC', None, 'IC', 'AA'])
+            self.assertEqual(action_names, ["RT", "WC", None, "IC", "AA"])
 
     def test_state_to_commit(self):
         state = StateToCommit(branch=self.b, domain=self.d, language=self.language, person=self.pr)
 
         for p in (self.pn, self.pt, self.pr):
             action_names = [a.name for a in state.get_available_actions(p)]
-            self.assertEqual(action_names, ['WC'])
+            self.assertEqual(action_names, ["WC"])
 
         for p in (self.pc, self.pcoo):
             action_names = [a.name for a in state.get_available_actions(p)]
-            self.assertEqual(action_names, ['RC', 'TR', 'UNDO', 'WC', None, 'IC', 'AA'])
+            self.assertEqual(action_names, ["RC", "TR", "UNDO", "WC", None, "IC", "AA"])
 
     @test_scratchdir
     def test_state_committing(self):
         state = StateProofreading(branch=self.b, domain=self.d, language=self.language, person=self.pr)
         state.save()
-        self.upload_file(state, 'UP')
-        action = Action.new_by_name('RC', person=self.pc)
-        action.apply_on(state, {'comment': "Reserve the commit"})
-        self.assertEqual(state.name, 'Committing')
+        self.upload_file(state, "UP")
+        action = Action.new_by_name("RC", person=self.pc)
+        action.apply_on(state, {"comment": "Reserve the commit"})
+        self.assertEqual(state.name, "Committing")
 
         for p in (self.pn, self.pt, self.pr):
             action_names = [a.name for a in state.get_available_actions(p)]
-            self.assertEqual(action_names, ['WC'])
+            self.assertEqual(action_names, ["WC"])
 
         action_names = [a.name for a in state.get_available_actions(self.pcoo)]
-        self.assertEqual(action_names, ['WC', 'UNDO', None, 'IC', 'AA'])
+        self.assertEqual(action_names, ["WC", "UNDO", None, "IC", "AA"])
 
         action_names = [a.name for a in state.get_available_actions(self.pc)]
-        self.assertEqual(action_names, ['IC', 'TR', 'UNDO', 'WC'])
+        self.assertEqual(action_names, ["IC", "TR", "UNDO", "WC"])
 
         # Setup as a writable repo
-        self.b.module.vcs_root = 'git gitlab gnome org:GNOME/%s.git' % self.b.module.name
+        self.b.module.vcs_root = "git gitlab gnome org:GNOME/%s.git" % self.b.module.name
         self.b.module.save()
         self.b.refresh_from_db()
         self.assertTrue(state.able_to_commit())
         action_names = [a.name for a in state.get_available_actions(self.pcoo)]
-        self.assertEqual(action_names, ['CI', 'WC', 'UNDO', None, 'IC', 'AA'])
+        self.assertEqual(action_names, ["CI", "WC", "UNDO", None, "IC", "AA"])
 
     def test_state_committed(self):
         state = StateCommitted(branch=self.b, domain=self.d, language=self.language, person=self.pc)
 
         for p in (self.pn, self.pt, self.pr):
             action_names = [a.name for a in state.get_available_actions(p)]
-            self.assertEqual(action_names, ['WC'])
+            self.assertEqual(action_names, ["WC"])
 
         for p in (self.pc, self.pcoo):
             action_names = [a.name for a in state.get_available_actions(p)]
-            self.assertEqual(action_names, ['AA', 'WC', None, 'IC', 'AA'])
+            self.assertEqual(action_names, ["AA", "WC", None, "IC", "AA"])
 
     def test_action_menu(self):
         state = StateNone(branch=self.b, domain=self.d, language=self.language)
         form = ActionForm(self.pt, state, state.get_available_actions(self.pt), False)
         self.assertHTMLEqual(
-            str(form['action']),
+            str(form["action"]),
             '<select id="id_action" name="action">'
             '<option value="RT">Reserve for translation</option>'
             '<option value="UT">Upload the new translation</option>'
             '<option value="WC">Write a comment</option>'
-            '</select>'
+            "</select>",
         )
         form = ActionForm(self.pcoo, state, state.get_available_actions(self.pcoo), False)
         self.assertHTMLEqual(
-            str(form['action']),
+            str(form["action"]),
             '<select id="id_action" name="action">'
             '<option value="RT">Reserve for translation</option>'
             '<option value="UT">Upload the new translation</option>'
@@ -245,7 +262,7 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
             '<option value="" disabled>--------</option>'
             '<option value="IC">Inform of submission</option>'
             '<option value="AA">Archive the actions</option>'
-            '</select>'
+            "</select>",
         )
 
     def test_action_wc(self):
@@ -253,47 +270,47 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
         # State may be initially unsaved
         prev_updated = state.updated
 
-        action = Action.new_by_name('WC', person=self.pt)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Hi 😉"})
+        action = Action.new_by_name("WC", person=self.pt)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Hi 😉"})
         self.assertEqual(len(mail.outbox), 1)
         self.assertEqual(mail.outbox[0].recipients(), [self.language.team.get_coordinators()[0].email])
-        self.assertEqual(mail.outbox[0].extra_headers, {settings.EMAIL_HEADER_NAME: 'Inactive'})
+        self.assertEqual(mail.outbox[0].extra_headers, {settings.EMAIL_HEADER_NAME: "Inactive"})
         # Second comment by someone else, mail sent to the other person
-        action = Action.new_by_name('WC', person=self.pr)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Great!"})
+        action = Action.new_by_name("WC", person=self.pr)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Great!"})
         self.assertEqual(len(mail.outbox), 2)
         self.assertEqual(mail.outbox[1].recipients(), [self.pt.email])
         # Test that submitting a comment without text generates a validation error
-        form = ActionForm(self.pt, state, [ActionWC()], True, QueryDict('action=WC&comment='))
+        form = ActionForm(self.pt, state, [ActionWC()], True, QueryDict("action=WC&comment="))
         self.assertTrue("A comment is needed" in str(form.errors))
         self.assertNotEqual(state.updated, prev_updated)
 
         # Test send comment to mailing list
         mail.outbox = []
-        action = Action.new_by_name('WC', person=self.pt)
-        action.apply_on(state, {'send_to_ml': True, 'comment': "Hi again!"})
+        action = Action.new_by_name("WC", person=self.pt)
+        action.apply_on(state, {"send_to_ml": True, "comment": "Hi again!"})
         self.assertTrue(action.sent_to_ml)
         self.assertEqual(len(mail.outbox), 1)
         self.assertEqual(mail.outbox[0].recipients(), [self.language.team.mailing_list])
         self.assertIn("Hi again!", mail.outbox[0].body)
-        self.assertEqual(mail.outbox[0].extra_headers, {settings.EMAIL_HEADER_NAME: 'Inactive'})
+        self.assertEqual(mail.outbox[0].extra_headers, {settings.EMAIL_HEADER_NAME: "Inactive"})
 
     def test_send_mail_translated(self):
-        team = Team.objects.create(name='zh', description='Chinese', mailing_list='zh example org')
-        zh_cn = Language.objects.create(name='Chinese', locale='zh_CN', team=team)
-        call_command('compile-trans', locale='zh_CN')
+        team = Team.objects.create(name="zh", description="Chinese", mailing_list="zh example org")
+        zh_cn = Language.objects.create(name="Chinese", locale="zh_CN", team=team)
+        call_command("compile-trans", locale="zh_CN")
 
         state = StateNone(branch=self.b, domain=self.d, language=zh_cn)
-        action = Action.new_by_name('WC', person=self.pn)
-        action.apply_on(state, {'send_to_ml': True, 'comment': "Comment"})
+        action = Action.new_by_name("WC", person=self.pn)
+        action.apply_on(state, {"send_to_ml": True, "comment": "Comment"})
         self.assertEqual(len(mail.outbox), 1)
         self.assertIn("您好,", mail.outbox[0].body)
 
     def test_action_rt(self):
         state = StateNone(branch=self.b, domain=self.d, language=self.language)
 
-        action = Action.new_by_name('RT', person=self.pt)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Reserved!"})
+        action = Action.new_by_name("RT", person=self.pt)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Reserved!"})
         self.assertIsInstance(state, StateTranslating)
 
     @test_scratchdir
@@ -308,10 +325,10 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
 
         file_path = Path(__file__).parent / "valid_po.po"
         with file_path.open() as test_file:
-            action = Action.new_by_name('UT', person=self.pt, file=File(test_file))
-            action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Done by translator."})
+            action = Action.new_by_name("UT", person=self.pt, file=File(test_file))
+            action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Done by translator."})
 
-        self.assertEqual(action.file.url, '/media/upload/gnome-hello-gnome-2-24-po-fr-%d.po' % state.id)
+        self.assertEqual(action.file.url, "/media/upload/gnome-hello-gnome-2-24-po-fr-%d.po" % state.id)
         self.files_to_clean.append(action.file.path)
         # Merged file will not be really produced as no pot file exists on the file system
         self.assertIsNone(action.merged_file)
@@ -321,18 +338,18 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
         self.assertEqual(len(mail.outbox), 1)
         self.assertEqual(mail.outbox[0].recipients(), [self.language.team.mailing_list])
         self.assertEqual(mail.outbox[0].subject, "%sgnome-hello - gnome-2-24" % 
settings.EMAIL_SUBJECT_PREFIX)
-        self.assertEqual(mail.outbox[0].extra_headers, {settings.EMAIL_HEADER_NAME: 'Translated'})
+        self.assertEqual(mail.outbox[0].extra_headers, {settings.EMAIL_HEADER_NAME: "Translated"})
 
         # Testing if the role was activated
         role = Role.objects.get(person=self.pt, team=self.language.team)
         self.assertTrue(role.is_active)
 
         # Setup as a writable repo and test Submit to repository is already available
-        self.b.module.vcs_root = 'git gitlab gnome org:GNOME/%s.git' % self.b.module.name
+        self.b.module.vcs_root = "git gitlab gnome org:GNOME/%s.git" % self.b.module.name
         self.b.module.save()
         self.assertEqual(
             [act.name for act in state.get_available_actions(self.pcoo)],
-            ['RP', 'UP', 'TR', 'RT', 'TC', 'CI', 'WC', None, 'IC', 'AA']
+            ["RP", "UP", "TR", "RT", "TC", "CI", "WC", None, "IC", "AA"],
         )
 
     @test_scratchdir
@@ -345,35 +362,36 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
 
         file_path = Path(__file__).parent / "valid_po.po"
         with file_path.open() as test_file:
-            action = Action.new_by_name('UT', person=self.pr, file=File(test_file))
+            action = Action.new_by_name("UT", person=self.pr, file=File(test_file))
             with self.assertRaisesRegex(Exception, "Action not allowed"):
-                action.apply_on(
-                    state,
-                    {'send_to_ml': False, 'comment': "Done by reviewer."}
-                )
+                action.apply_on(state, {"send_to_ml": False, "comment": "Done by reviewer."})
 
     def test_action_rp(self):
         state = StateTranslated(branch=self.b, domain=self.d, language=self.language)
         state.save()
 
-        action = Action.new_by_name('RP', person=self.pr)
-        action.apply_on(state, {
-            'send_to_ml': action.send_mail_to_ml, 'comment': "",
-        })
+        action = Action.new_by_name("RP", person=self.pr)
+        action.apply_on(
+            state,
+            {
+                "send_to_ml": action.send_mail_to_ml,
+                "comment": "",
+            },
+        )
         self.assertIsInstance(state, StateProofreading)
         self.assertEqual(len(mail.outbox), 0)
 
     def test_action_rp_with_comment(self):
         state = StateTranslated.objects.create(branch=self.b, domain=self.d, language=self.language)
-        action = Action.new_by_name('WC', person=self.pt)
-        action.apply_on(state, {'send_to_ml': False, 'comment': "Hi!"})
+        action = Action.new_by_name("WC", person=self.pt)
+        action.apply_on(state, {"send_to_ml": False, "comment": "Hi!"})
         self.assertFalse(action.sent_to_ml)
         # At least the coordinator receives the message
         self.assertEqual(len(mail.outbox), 1)
         self.assertEqual(mail.outbox[0].recipients(), [self.language.team.get_coordinators()[0].email])
 
-        action = Action.new_by_name('RP', person=self.pr)
-        action.apply_on(state, {'send_to_ml': False, 'comment': "I'm reviewing this!"})
+        action = Action.new_by_name("RP", person=self.pr)
+        action.apply_on(state, {"send_to_ml": False, "comment": "I'm reviewing this!"})
         # If a comment is set, a message should be sent
         self.assertEqual(len(mail.outbox), 2)
         self.assertEqual(mail.outbox[1].recipients(), [self.pt.email])
@@ -382,11 +400,11 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
         state = StateProofreading(branch=self.b, domain=self.d, language=self.language, person=self.pr)
         state.save()
 
-        test_file = ContentFile('test content')
-        test_file.name = 'mytestfile.po'
+        test_file = ContentFile("test content")
+        test_file.name = "mytestfile.po"
 
-        action = Action.new_by_name('UP', person=self.pr, file=test_file)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Done."})
+        action = Action.new_by_name("UP", person=self.pr, file=test_file)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Done."})
         self.files_to_clean.append(action.file.path)
         self.assertIsInstance(state, StateProofread)
         # Mail sent to mailing list
@@ -395,13 +413,13 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
 
         # Comment made by someone else, file reviewed again, checkbox "Send to mailing list" unckecked
         # => mail sent to the comment author
-        action = Action.new_by_name('WC', person=self.pt)
-        action.apply_on(state, {'send_to_ml': False, 'comment': "Hi!"})
-        action = Action.new_by_name('RP', person=self.pr)
-        action.apply_on(state, {'send_to_ml': False, 'comment': "Reserved by a reviewer!"})
+        action = Action.new_by_name("WC", person=self.pt)
+        action.apply_on(state, {"send_to_ml": False, "comment": "Hi!"})
+        action = Action.new_by_name("RP", person=self.pr)
+        action.apply_on(state, {"send_to_ml": False, "comment": "Reserved by a reviewer!"})
         mail.outbox = []
-        action = Action.new_by_name('UP', person=self.pr, file=test_file)
-        action.apply_on(state, {'send_to_ml': False, 'comment': "Done second time."})
+        action = Action.new_by_name("UP", person=self.pr, file=test_file)
+        action.apply_on(state, {"send_to_ml": False, "comment": "Done second time."})
         self.assertEqual(len(mail.outbox), 1)
         self.assertEqual(mail.outbox[0].recipients(), [self.pt.email])
 
@@ -409,50 +427,50 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
         state = StateProofread(branch=self.b, domain=self.d, language=self.language)
         state.save()
 
-        action = Action.new_by_name('TC', person=self.pr)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Ready!"})
+        action = Action.new_by_name("TC", person=self.pr)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Ready!"})
         self.assertIsInstance(state, StateToCommit)
 
     def test_action_rc(self):
         state = StateToCommit(branch=self.b, domain=self.d, language=self.language)
         state.save()
 
-        action = Action.new_by_name('RC', person=self.pc)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "This work is mine!"})
+        action = Action.new_by_name("RC", person=self.pc)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "This work is mine!"})
         self.assertIsInstance(state, StateCommitting)
 
     @test_scratchdir
     def test_action_ci(self):
         # Setup as a writable repo
-        self.b.module.vcs_root = 'git gitlab gnome org:GNOME/%s.git' % self.b.module.name
+        self.b.module.vcs_root = "git gitlab gnome org:GNOME/%s.git" % self.b.module.name
         self.b.module.save()
         Branch.checkout_on_creation = False
-        master = Branch(name='master', module=self.m)
+        master = Branch(name="master", module=self.m)
         master.save(update_statistics=False)
         state = StateProofreading(branch=self.b, domain=self.d, language=self.language, person=self.pr)
         state.save()
 
-        action = Action.new_by_name('WC', person=self.pt)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Hi!"})
+        action = Action.new_by_name("WC", person=self.pt)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Hi!"})
         # Adding users with incomplete profiles
-        pers_no_full_name = Person.objects.create(username='ûsername')
-        action = Action.new_by_name('WC', person=pers_no_full_name)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Hello!"})
-        pers_no_email = Person.objects.create(username='noemail', first_name="Sven", last_name="Brkc")
-        action = Action.new_by_name('WC', person=pers_no_email)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Hello!"})
+        pers_no_full_name = Person.objects.create(username="ûsername")
+        action = Action.new_by_name("WC", person=pers_no_full_name)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Hello!"})
+        pers_no_email = Person.objects.create(username="noemail", first_name="Sven", last_name="Brkc")
+        action = Action.new_by_name("WC", person=pers_no_email)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Hello!"})
 
-        self.upload_file(state, 'UP')
+        self.upload_file(state, "UP")
 
         self.assertIn(ActionCI, map(type, state.get_available_actions(self.pcoo)))
 
         form = ActionForm(self.pcoo, state, state.get_available_actions(self.pcoo), True)
-        self.assertEqual(len(form.fields['author'].choices), 6)
-        self.assertEqual(form.fields['author'].initial, self.pr)
-        self.assertIn('sync_master', form.fields)
-        self.assertEqual(form.fields['sync_master'].label, "Sync with master")
+        self.assertEqual(len(form.fields["author"].choices), 6)
+        self.assertEqual(form.fields["author"].initial, self.pr)
+        self.assertIn("sync_master", form.fields)
+        self.assertEqual(form.fields["sync_master"].label, "Sync with master")
         self.assertHTMLEqual(
-            str(form['author']),
+            str(form["author"]),
             '<select id="id_author" name="author">'
             '<option value="">---------</option>'
             '<option disabled value="%d">ûsername (full name missing)</option>'
@@ -460,9 +478,14 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
             '<option value="%d">John Coordinator</option>'
             '<option selected value="%d">John Reviewer</option>'
             '<option value="%d">John Translator</option>'
-            '</select>' % (
-                pers_no_full_name.pk, pers_no_email.pk, self.pcoo.pk, self.pr.pk, self.pt.pk,
-            )
+            "</select>"
+            % (
+                pers_no_full_name.pk,
+                pers_no_email.pk,
+                self.pcoo.pk,
+                self.pr.pk,
+                self.pt.pk,
+            ),
         )
 
     @test_scratchdir
@@ -470,42 +493,34 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
         """
         Test that a commit works even when nobody has full name, but without --author flag.
         """
-        pers = Person.objects.create(email='user example org', username='username_only')
-        Role.objects.create(team=self.t, person=pers, role='reviewer')
+        pers = Person.objects.create(email="user example org", username="username_only")
+        Role.objects.create(team=self.t, person=pers, role="reviewer")
         self.m.vcs_root = "git gitlab gnome org:GNOME/gnome-hello.git"
         self.m.save()
 
         state = StateProofreading(branch=self.b, domain=self.d, language=self.language, person=pers)
         state.save()
         # Adding two comments from the commit author, as this might trigger a form error
-        action = Action.new_by_name('WC', person=self.pcoo)
-        action.apply_on(state, {'send_to_ml': False, 'comment': "Looks good"})
-        action = Action.new_by_name('WC', person=self.pcoo)
-        action.apply_on(state, {'send_to_ml': False, 'comment': "Looks good too"})
-
-        self.upload_file(state, 'UP', pers=pers)
-        post_data = {
-            'action': 'CI',
-            'author': '',
-            'comment': '',
-            'send_to_ml': True
-        }
+        action = Action.new_by_name("WC", person=self.pcoo)
+        action.apply_on(state, {"send_to_ml": False, "comment": "Looks good"})
+        action = Action.new_by_name("WC", person=self.pcoo)
+        action.apply_on(state, {"send_to_ml": False, "comment": "Looks good too"})
+
+        self.upload_file(state, "UP", pers=pers)
+        post_data = {"action": "CI", "author": "", "comment": "", "send_to_ml": True}
         form = ActionForm(self.pcoo, state, state.get_available_actions(self.pcoo), True, post_data)
         # Missing author
         self.assertFalse(form.is_valid())
-        post_data['author'] = str(self.pcoo.pk)
+        post_data["author"] = str(self.pcoo.pk)
         form = ActionForm(self.pcoo, state, state.get_available_actions(self.pcoo), True, post_data)
         self.assertTrue(form.is_valid())
         # path needed when copying file to commit
-        (self.b.co_path / 'po').mkdir(parents=True, exist_ok=True)
-        with PatchShellCommand(only=['git ']) as cmds:
-            action = Action.new_by_name('CI', person=self.pcoo)
+        (self.b.co_path / "po").mkdir(parents=True, exist_ok=True)
+        with PatchShellCommand(only=["git "]) as cmds:
+            action = Action.new_by_name("CI", person=self.pcoo)
             msg = action.apply_on(state, form.cleaned_data)
-        self.assertIn(
-            'git commit -m Update French translation --author John Coordinator <jcoo imthebigboss fr>',
-            cmds
-        )
-        self.assertEqual(msg, 'The file has been successfully committed to the repository.')
+        self.assertIn("git commit -m Update French translation --author John Coordinator <jcoo imthebigboss 
fr>", cmds)
+        self.assertEqual(msg, "The file has been successfully committed to the repository.")
         state.refresh_from_db()
         # All actions should have been archived
         self.assertEqual(state.action_set.count(), 0)
@@ -515,28 +530,28 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
         state.save()
 
         # Upload a new file
-        action_file = self.upload_file(state, 'UP')
+        action_file = self.upload_file(state, "UP")
         self.assertEqual(len(mail.outbox), 1)  # Mail sent to mailing list
         mail.outbox = []
 
         file_path = settings.MEDIA_ROOT / action_file.name
         self.assertTrue(file_path.exists())
 
-        action = Action.new_by_name('TC', person=self.pc)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': ''})
+        action = Action.new_by_name("TC", person=self.pc)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": ""})
         self.assertEqual(len(mail.outbox), 1)  # Mail sent to committers
         mail.outbox = []
 
-        action = Action.new_by_name('RC', person=self.pc)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': ''})
+        action = Action.new_by_name("RC", person=self.pc)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": ""})
 
-        action = Action.new_by_name('IC', person=self.pc)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Committed."})
+        action = Action.new_by_name("IC", person=self.pc)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Committed."})
         # Mail sent to mailing list
         self.assertEqual(len(mail.outbox), 1)
         self.assertEqual(mail.outbox[0].recipients(), [self.language.team.mailing_list])
         # Team is French (but translations may not be compiled/up-to-date)
-        self.assertTrue('Commité' in mail.outbox[0].body or "Committed" in mail.outbox[0].body)
+        self.assertTrue("Commité" in mail.outbox[0].body or "Committed" in mail.outbox[0].body)
 
         self.assertIsInstance(state, StateNone)
         self.assertFalse(file_path.exists(), "%s not deleted" % file_path)
@@ -551,18 +566,22 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
         state = StateTranslated(branch=self.b, domain=self.d, language=self.language)
         state.save()
 
-        action = Action.new_by_name('TR', person=self.pc)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Bad work :-/"})
+        action = Action.new_by_name("TR", person=self.pc)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Bad work :-/"})
         self.assertIsInstance(state, StateToReview)
 
     def test_action_aa(self):
         state = StateCommitted(branch=self.b, domain=self.d, language=self.language, person=self.pr)
         state.save()
 
-        action = Action.new_by_name('AA', person=self.pc)
-        action.apply_on(state, {
-            'send_to_ml': action.send_mail_to_ml, 'comment': "I don't want to disappear :)",
-        })
+        action = Action.new_by_name("AA", person=self.pc)
+        action.apply_on(
+            state,
+            {
+                "send_to_ml": action.send_mail_to_ml,
+                "comment": "I don't want to disappear :)",
+            },
+        )
 
         state = State.objects.get(branch=self.b, domain=self.d, language=self.language)
         self.assertIsInstance(state, StateNone)
@@ -571,69 +590,72 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
     def test_action_undo(self):
         state = StateNone(branch=self.b, domain=self.d, language=self.language)
 
-        action = Action.new_by_name('RT', person=self.pt)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Reserved!"})
+        action = Action.new_by_name("RT", person=self.pt)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Reserved!"})
 
-        action = Action.new_by_name('UNDO', person=self.pt)
-        action.apply_on(state, {
-            'send_to_ml': action.send_mail_to_ml, 'comment': "Ooops! I don't want to do that. Sorry.",
-        })
+        action = Action.new_by_name("UNDO", person=self.pt)
+        action.apply_on(
+            state,
+            {
+                "send_to_ml": action.send_mail_to_ml,
+                "comment": "Ooops! I don't want to do that. Sorry.",
+            },
+        )
 
-        self.assertEqual(state.name, 'None')
+        self.assertEqual(state.name, "None")
 
-        action = Action.new_by_name('RT', person=self.pt)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Translating"})
+        action = Action.new_by_name("RT", person=self.pt)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Translating"})
 
-        action = Action.new_by_name('UT', person=self.pt)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Translated"})
+        action = Action.new_by_name("UT", person=self.pt)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Translated"})
 
-        action = Action.new_by_name('RT', person=self.pt)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Reserved!"})
+        action = Action.new_by_name("RT", person=self.pt)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Reserved!"})
 
-        action = Action.new_by_name('UNDO', person=self.pt)
-        action.apply_on(state, {
-            'send_to_ml': action.send_mail_to_ml,
-            'comment': "Ooops! I don't want to do that. Sorry."
-        })
+        action = Action.new_by_name("UNDO", person=self.pt)
+        action.apply_on(
+            state, {"send_to_ml": action.send_mail_to_ml, "comment": "Ooops! I don't want to do that. 
Sorry."}
+        )
         self.assertEqual(action.comment, "Ooops! I don't want to do that. Sorry.")
 
-        self.assertEqual(state.name, 'Translated')
+        self.assertEqual(state.name, "Translated")
 
-        action = Action.new_by_name('RT', person=self.pt)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Translating 1"})
+        action = Action.new_by_name("RT", person=self.pt)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Translating 1"})
 
-        action = Action.new_by_name('UNDO', person=self.pt)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Undo 1"})
+        action = Action.new_by_name("UNDO", person=self.pt)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Undo 1"})
 
-        action = Action.new_by_name('RT', person=self.pt)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Translating 2"})
+        action = Action.new_by_name("RT", person=self.pt)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Translating 2"})
 
-        action = Action.new_by_name('UNDO', person=self.pt)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Undo 2"})
+        action = Action.new_by_name("UNDO", person=self.pt)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Undo 2"})
 
-        self.assertEqual(state.name, 'Translated')
+        self.assertEqual(state.name, "Translated")
 
     def test_action_on_archived_module(self):
         state = StateNone.objects.create(branch=self.b, domain=self.d, language=self.language)
-        action = Action.new_by_name('WC', person=self.pt)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Hi!"})
+        action = Action.new_by_name("WC", person=self.pt)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Hi!"})
         self.m.archived = True
         self.m.save()
 
         # For an archived module, the only available action is to archive the state by a coord.
         self.assertEqual(state.get_available_actions(self.pt), [])
-        self.assertEqual([a.name for a in state.get_available_actions(self.pcoo)], ['AA'])
+        self.assertEqual([a.name for a in state.get_available_actions(self.pcoo)], ["AA"])
 
-        action = Action.new_by_name('AA', person=self.pcoo)
-        action.apply_on(state, {'send_to_ml': False})
-        self.assertEqual(state.name, 'None')
+        action = Action.new_by_name("AA", person=self.pcoo)
+        action.apply_on(state, {"send_to_ml": False})
+        self.assertEqual(state.name, "None")
 
     def test_delete(self):
-        """ Test that a whole module tree can be properly deleted """
+        """Test that a whole module tree can be properly deleted"""
         state = StateNone(branch=self.b, domain=self.d, language=self.language)
 
-        action = Action.new_by_name('WC', person=self.pt)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Hi!"})
+        action = Action.new_by_name("WC", person=self.pt)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Hi!"})
 
         self.m.delete()
         self.assertEqual(Action.objects.all().count(), 0)
@@ -644,50 +666,53 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
         self.assertEqual(State.objects.all().count(), 0)
 
     def test_delete_statistics(self):
-        """ Test clean_dangling_states receiver """
+        """Test clean_dangling_states receiver"""
         po_stat = Statistics.objects.create(branch=self.b, domain=self.d, language=self.language)
         StateTranslating.objects.create(branch=self.b, domain=self.d, language=self.language, person=self.pt)
         po_stat.delete()
         self.assertEqual(State.objects.all().count(), 0)
 
     def test_vertimus_view(self):
-        url = reverse('vertimus_by_ids', args=[self.b.id, self.d.id, self.language.id])
+        url = reverse("vertimus_by_ids", args=[self.b.id, self.d.id, self.language.id])
         response = self.client.get(url)
         self.assertNotContains(response, '<option value="WC">')
         self.assertContains(
             response,
-            '<span class="num1">      0</span><span class="num2">     0</span><span class="num3">     
0</span>'
+            '<span class="num1">      0</span><span class="num2">     0</span><span class="num3">     
0</span>',
         )
 
-        self.client.login(username=self.pn.username, password='password')
+        self.client.login(username=self.pn.username, password="password")
         response = self.client.get(url)
         self.assertContains(response, '<option value="WC">')
 
-        response = self.client.post(url, data={
-            'action': 'WC',
-            'comment': 'Graçias', 'send_to_ml': '',
-        }, follow=True)
-        self.assertContains(response, 'Graçias')
+        response = self.client.post(
+            url,
+            data={
+                "action": "WC",
+                "comment": "Graçias",
+                "send_to_ml": "",
+            },
+            follow=True,
+        )
+        self.assertContains(response, "Graçias")
 
     def test_vertimus_view_on_going_activities(self):
-        master = Branch(name='master', module=self.m)
+        master = Branch(name="master", module=self.m)
         # Block the update of Statistics by the thread
         master.save(update_statistics=False)
         Statistics.objects.create(branch=master, domain=self.d, language=None)
         dom_alt = Domain.objects.create(
-            module=self.m, name='po',
-            description='UI translations',
-            dtype='ui', layout='po/{lang}.po'
+            module=self.m, name="po", description="UI translations", dtype="ui", layout="po/{lang}.po"
         )
         StateNone.objects.create(branch=master, domain=self.d, language=self.language)
         StateNone.objects.create(branch=self.b, domain=dom_alt, language=self.language)
 
-        response = self.client.get(reverse('vertimus_by_ids', args=[master.pk, self.d.pk, self.language.pk]))
+        response = self.client.get(reverse("vertimus_by_ids", args=[master.pk, self.d.pk, self.language.pk]))
         self.assertNotContains(response, "On-going activities in same module:")
 
         state = State.objects.get(branch=self.b, domain=dom_alt, language=self.language)
         state.change_state(StateTranslated)
-        response = self.client.get(reverse('vertimus_by_ids', args=[master.pk, self.d.pk, self.language.pk]))
+        response = self.client.get(reverse("vertimus_by_ids", args=[master.pk, self.d.pk, self.language.pk]))
         self.assertContains(response, "On-going activities in same module:")
 
     @test_scratchdir
@@ -696,39 +721,36 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
         state.save()
         # Ensure pot file exists (used for merged file and by the diff view)
         potfile, errs = self.d.generate_pot_file(self.b)
-        pot_location = self.b.output_dir(self.d.dtype) / ('%s.%s.pot' % (self.d.potbase(), self.b.name))
+        pot_location = self.b.output_dir(self.d.dtype) / ("%s.%s.pot" % (self.d.potbase(), self.b.name))
         shutil.copyfile(str(potfile), str(pot_location))
 
-        action = Action.new_by_name('RT', person=self.pt)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': ""})
-        with (Path(__file__).parent / "valid_po.po").open('rb') as fh:
-            action = Action.new_by_name('UT', person=self.pt, file=File(fh))
-            action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Done."})
+        action = Action.new_by_name("RT", person=self.pt)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": ""})
+        with (Path(__file__).parent / "valid_po.po").open("rb") as fh:
+            action = Action.new_by_name("UT", person=self.pt, file=File(fh))
+            action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Done."})
         action_history = Action.get_action_history(state=state)
-        diff_url = reverse('vertimus_diff', args=[action_history[-1][0].id, '0', 0])
+        diff_url = reverse("vertimus_diff", args=[action_history[-1][0].id, "0", 0])
         response = self.client.get(diff_url)
-        self.assertContains(
-            response,
-            '<a href="%s">Latest POT file</a>' % str(pot_location).split('scratch')[1]
-        )
+        self.assertContains(response, '<a href="%s">Latest POT file</a>' % 
str(pot_location).split("scratch")[1])
         # Should also work if action persons were deleted
         self.pt.delete()
         response = self.client.get(diff_url)
-        self.assertContains(response, 'Latest POT file')
+        self.assertContains(response, "Latest POT file")
 
     def test_uploaded_file_validation(self):
         # Test a non valid po file
-        post_content = QueryDict('action=WC&comment=Test1')
-        post_file = MultiValueDict({'file': [SimpleUploadedFile('filename.po', b'Not valid po file 
content')]})
+        post_content = QueryDict("action=WC&comment=Test1")
+        post_file = MultiValueDict({"file": [SimpleUploadedFile("filename.po", b"Not valid po file 
content")]})
         form = ActionForm(self.pt, None, [ActionWC()], True, post_content, post_file)
-        self.assertTrue('file' in form.errors)
-        post_file = MultiValueDict({'file': [SimpleUploadedFile('filename.po', 'Niña'.encode('latin-1'))]})
+        self.assertTrue("file" in form.errors)
+        post_file = MultiValueDict({"file": [SimpleUploadedFile("filename.po", "Niña".encode("latin-1"))]})
         form = ActionForm(self.pt, None, [ActionWC()], True, post_content, post_file)
-        self.assertTrue('file' in form.errors)
+        self.assertTrue("file" in form.errors)
 
         # Test a valid po file
-        with (Path(__file__).parent / "valid_po.po").open('rb') as fh:
-            post_file = MultiValueDict({'file': [File(fh)]})
+        with (Path(__file__).parent / "valid_po.po").open("rb") as fh:
+            post_file = MultiValueDict({"file": [File(fh)]})
             form = ActionForm(self.pt, None, [ActionWC()], True, post_content, post_file)
             self.assertTrue(form.is_valid())
 
@@ -739,35 +761,30 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
     def test_feeds(self):
         state = StateNone(branch=self.b, domain=self.d, language=self.language)
 
-        action = Action.new_by_name('RT', person=self.pt)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Translating"})
+        action = Action.new_by_name("RT", person=self.pt)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Translating"})
 
-        response = self.client.get(reverse('lang_feed', args=[self.language.locale]))
+        response = self.client.get(reverse("lang_feed", args=[self.language.locale]))
         doc = parseString(response.content)
-        self.assertEqual(doc.childNodes[0].tagName, 'rss')
+        self.assertEqual(doc.childNodes[0].tagName, "rss")
+        self.assertEqual(doc.childNodes[0].getAttribute("xmlns:atom"), "http://www.w3.org/2005/Atom";)
         self.assertEqual(
-            doc.childNodes[0].getAttribute('xmlns:atom'), "http://www.w3.org/2005/Atom";
+            doc.getElementsByTagName("title")[1].toxml(),
+            """<title>po (gnome-hello/User Interface) - gnome-hello (gnome-2-24) - Reserve for 
translation\n</title>""",
         )
         self.assertEqual(
-            doc.getElementsByTagName('title')[1].toxml(),
-            """<title>po (gnome-hello/User Interface) - gnome-hello (gnome-2-24) - Reserve for 
translation\n</title>"""
-        )
-        self.assertEqual(
-            doc.getElementsByTagName('guid')[0].toxml(),
-            """<guid>http://testserver/vertimus/gnome-hello/gnome-2-24/po/fr/#%d</guid>""" % action.pk
+            doc.getElementsByTagName("guid")[0].toxml(),
+            """<guid>http://testserver/vertimus/gnome-hello/gnome-2-24/po/fr/#%d</guid>""" % action.pk,
         )
 
-        response = self.client.get(reverse('team_feed', args=[self.language.team.name]))
+        response = self.client.get(reverse("team_feed", args=[self.language.team.name]))
         self.assertContains(
             response,
-            """<title>po (gnome-hello/User Interface) - gnome-hello (gnome-2-24) - Reserve for 
translation\n</title>"""
+            """<title>po (gnome-hello/User Interface) - gnome-hello (gnome-2-24) - Reserve for 
translation\n</title>""",
         )
 
     def test_activity_summary(self):
-        StateTranslating.objects.create(
-            branch=self.b, domain=self.d,
-            language=self.language,
-            person=self.pt)
+        StateTranslating.objects.create(branch=self.b, domain=self.d, language=self.language, person=self.pt)
 
         response = self.client.get(reverse("activity_by_language", args=[self.language.locale]))
         self.assertContains(response, self.m.description)
@@ -778,19 +795,19 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
         state = StateProofreading(branch=self.b, domain=self.d, language=self.language, person=self.pr)
         state.save()
         with valid_path.open() as test_file:
-            action = Action.new_by_name('UT', person=self.pt, file=File(test_file))
+            action = Action.new_by_name("UT", person=self.pt, file=File(test_file))
             action.state_db = state
             action.file.save(os.path.basename(action.file.name), action.file, save=False)
         action.merged_file = None
         action.save()
-        response = self.client.get(reverse('action-quality-check', args=[action.pk]))
+        response = self.client.get(reverse("action-quality-check", args=[action.pk]))
         self.assertContains(response, "The po file looks good!")
 
         po_stat = Statistics.objects.create(branch=self.b, domain=self.d, language=self.language)
         os.mkdir(os.path.dirname(po_stat.po_path()))
-        with valid_path.open() as valid_file, open(po_stat.po_path(), 'w') as fh:
+        with valid_path.open() as valid_file, open(po_stat.po_path(), "w") as fh:
             fh.write(valid_file.read())
-        response = self.client.get(reverse('stats-quality-check', args=[po_stat.pk]))
+        response = self.client.get(reverse("stats-quality-check", args=[po_stat.pk]))
         self.assertContains(response, "The po file looks good!")
 
     def test_mysql(self):
@@ -798,29 +815,37 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
         state = StateNone(branch=self.b, domain=self.d, language=self.language)
         state.save()
 
-        action = Action.new_by_name('RT', person=self.pr, comment="Reserved!")
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Reserved!"})
+        action = Action.new_by_name("RT", person=self.pr, comment="Reserved!")
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Reserved!"})
 
-        action = Action.new_by_name('UNDO', person=self.pr)
-        action.apply_on(state, {
-            'send_to_ml': action.send_mail_to_ml, 'comment': "Ooops! I don't want to do that. Sorry.",
-        })
+        action = Action.new_by_name("UNDO", person=self.pr)
+        action.apply_on(
+            state,
+            {
+                "send_to_ml": action.send_mail_to_ml,
+                "comment": "Ooops! I don't want to do that. Sorry.",
+            },
+        )
 
-        action = Action.new_by_name('RT', person=self.pr)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Translating"})
+        action = Action.new_by_name("RT", person=self.pr)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Translating"})
 
-        action = Action.new_by_name('UT', person=self.pr)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Translated"})
+        action = Action.new_by_name("UT", person=self.pr)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Translated"})
 
-        action = Action.new_by_name('RP', person=self.pr)
-        action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Proofreading"})
+        action = Action.new_by_name("RP", person=self.pr)
+        action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Proofreading"})
 
-        action = Action.new_by_name('UNDO', person=self.pr)
-        action.apply_on(state, {
-            'send_to_ml': action.send_mail_to_ml, 'comment': "Ooops! I don't want to do that. Sorry.",
-        })
+        action = Action.new_by_name("UNDO", person=self.pr)
+        action.apply_on(
+            state,
+            {
+                "send_to_ml": action.send_mail_to_ml,
+                "comment": "Ooops! I don't want to do that. Sorry.",
+            },
+        )
 
-        actions_db = Action.objects.filter(state_db__id=state.id).exclude(name='WC').order_by('-id')
+        actions_db = Action.objects.filter(state_db__id=state.id).exclude(name="WC").order_by("-id")
 
         # So the last action is UNDO
         self.assertIsInstance(actions_db[0], ActionUNDO)
@@ -833,58 +858,52 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
 class DocsBuildingTests(TeamsAndRolesMixin, TestModuleBase):
     def setUp(self):
         super().setUp()
-        html_dir = settings.SCRATCHDIR / 'HTML'
+        html_dir = settings.SCRATCHDIR / "HTML"
         if html_dir.exists():
             shutil.rmtree(str(html_dir))
 
     def test_doc_building(self):
         dom = Domain.objects.create(
-            module=self.mod, name='help', description='User Guide', dtype='doc',
-            layout='help_mallard/{lang}/{lang}.po'
+            module=self.mod, name="help", description="User Guide", dtype="doc", 
layout="help_mallard/{lang}/{lang}.po"
         )
         state = StateTranslating(branch=self.branch, domain=dom, language=self.language, person=self.pt)
         state.save()
 
         file_path = Path(__file__).parent / "gnome-hello.help.fr.po"
         with file_path.open() as test_file:
-            action = Action.new_by_name('UT', person=self.pt, file=File(test_file))
-            action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Done by translator."})
+            action = Action.new_by_name("UT", person=self.pt, file=File(test_file))
+            action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Done by translator."})
         self.assertTrue(action.can_build)
         self.assertIsNone(action.build_url)
-        with patch('stats.models.Branch.checkout'):
-            response = self.client.post(reverse('action-build-help', args=[action.pk]))
+        with patch("stats.models.Branch.checkout"):
+            response = self.client.post(reverse("action-build-help", args=[action.pk]))
             self.assertEqual(response.status_code, 403)
             self.client.force_login(self.pt)
-            response = self.client.post(reverse('action-build-help', args=[action.pk]))
-        self.assertRedirects(
-            response, '/HTML/%d/index.html' % action.pk, fetch_redirect_response=False
-        )
-        self.assertEqual(action.build_url, '/HTML/%d/index.html' % action.pk)
-        index_file = settings.SCRATCHDIR / 'HTML' / str(action.pk) / 'index.html'
-        with index_file.open('r') as ifile:
+            response = self.client.post(reverse("action-build-help", args=[action.pk]))
+        self.assertRedirects(response, "/HTML/%d/index.html" % action.pk, fetch_redirect_response=False)
+        self.assertEqual(action.build_url, "/HTML/%d/index.html" % action.pk)
+        index_file = settings.SCRATCHDIR / "HTML" / str(action.pk) / "index.html"
+        with index_file.open("r") as ifile:
             self.assertIn('<h2><span class="title">À propos</span></h2>', ifile.read())
 
     def test_docbook_building(self):
         dom = Domain.objects.create(
-            module=self.mod, name='help', description='User Guide', dtype='doc',
-            layout='help_docbook/{lang}/{lang}.po'
+            module=self.mod, name="help", description="User Guide", dtype="doc", 
layout="help_docbook/{lang}/{lang}.po"
         )
         state = StateTranslating(branch=self.branch, domain=dom, language=self.language, person=self.pt)
         state.save()
 
         file_path = Path(__file__).parent / "gnome-hello.help.fr.po"
         with file_path.open() as test_file:
-            action = Action.new_by_name('UT', person=self.pt, file=File(test_file))
-            action.apply_on(state, {'send_to_ml': action.send_mail_to_ml, 'comment': "Done by translator."})
+            action = Action.new_by_name("UT", person=self.pt, file=File(test_file))
+            action.apply_on(state, {"send_to_ml": action.send_mail_to_ml, "comment": "Done by translator."})
         self.assertTrue(action.can_build)
         self.assertIsNone(action.build_url)
         self.client.force_login(self.pt)
-        with patch('stats.models.Branch.checkout'):
-            response = self.client.post(reverse('action-build-help', args=[action.pk]))
-        self.assertRedirects(
-            response, '/HTML/%d/index.html' % action.pk, fetch_redirect_response=False
-        )
-        self.assertEqual(action.build_url, '/HTML/%d/index.html' % action.pk)
-        index_file = settings.SCRATCHDIR / 'HTML' / str(action.pk) / 'index.html'
-        with index_file.open('r') as ifile:
+        with patch("stats.models.Branch.checkout"):
+            response = self.client.post(reverse("action-build-help", args=[action.pk]))
+        self.assertRedirects(response, "/HTML/%d/index.html" % action.pk, fetch_redirect_response=False)
+        self.assertEqual(action.build_url, "/HTML/%d/index.html" % action.pk)
+        index_file = settings.SCRATCHDIR / "HTML" / str(action.pk) / "index.html"
+        with index_file.open("r") as ifile:
             self.assertIn('<h2><span class="title">À propos</span></h2>', ifile.read())
diff --git a/vertimus/urls.py b/vertimus/urls.py
index f35c3224..c024fbc7 100644
--- a/vertimus/urls.py
+++ b/vertimus/urls.py
@@ -3,39 +3,24 @@ from django.urls import path
 from damnedlies.urls import module_branch_domain
 from vertimus import views
 
-
 urlpatterns = [
-    path('<int:stats_id>/<int:lang_id>/',
-         views.vertimus_by_stats_id,
-         name='vertimus_by_stats_id'),
-    path('<int:branch_id>/<int:domain_id>/<int:language_id>/',
-         views.vertimus_by_ids,
-         name='vertimus_by_ids'),
-    path('diff/<int:action_id_1>/<int:action_id_2>/<int:level>/',
-         views.vertimus_diff,
-         name='vertimus_diff'),
-    path('uploads/%s/<locale:locale_name>)/latest/' % module_branch_domain,
-         views.latest_uploaded_po,
-         name='latest_uploaded_po'),
-    path('%s/<locale:locale_name>/level<int:level>/' % module_branch_domain,
-         views.vertimus_by_names,
-         name='vertimus_archives_by_names'),
-    path('%s/<locale:locale_name>/' % module_branch_domain,
-         views.vertimus_by_names,
-         name='vertimus_by_names'),
-    path('<locale:locale>/activity_summary/',
-         views.activity_by_language,
-         name='activity_by_language'),
-    path('action/<int:action_pk>/qcheck/',
-         views.QualityCheckView.as_view(),
-         name='action-quality-check'),
-    path('stats/<int:stats_pk>/qcheck/',
-         views.QualityCheckView.as_view(),
-         name='stats-quality-check'),
-    path('stats/<int:stats_pk>/msgiddiff/',
-         views.MsgiddiffView.as_view(),
-         name='stats-msgiddiff'),
-    path('action/<int:action_pk>/build_help/',
-         views.BuildTranslatedDocsView.as_view(),
-         name='action-build-help'),
+    path("<int:stats_id>/<int:lang_id>/", views.vertimus_by_stats_id, name="vertimus_by_stats_id"),
+    path("<int:branch_id>/<int:domain_id>/<int:language_id>/", views.vertimus_by_ids, 
name="vertimus_by_ids"),
+    path("diff/<int:action_id_1>/<int:action_id_2>/<int:level>/", views.vertimus_diff, name="vertimus_diff"),
+    path(
+        "uploads/%s/<locale:locale_name>)/latest/" % module_branch_domain,
+        views.latest_uploaded_po,
+        name="latest_uploaded_po",
+    ),
+    path(
+        "%s/<locale:locale_name>/level<int:level>/" % module_branch_domain,
+        views.vertimus_by_names,
+        name="vertimus_archives_by_names",
+    ),
+    path("%s/<locale:locale_name>/" % module_branch_domain, views.vertimus_by_names, 
name="vertimus_by_names"),
+    path("<locale:locale>/activity_summary/", views.activity_by_language, name="activity_by_language"),
+    path("action/<int:action_pk>/qcheck/", views.QualityCheckView.as_view(), name="action-quality-check"),
+    path("stats/<int:stats_pk>/qcheck/", views.QualityCheckView.as_view(), name="stats-quality-check"),
+    path("stats/<int:stats_pk>/msgiddiff/", views.MsgiddiffView.as_view(), name="stats-msgiddiff"),
+    path("action/<int:action_pk>/build_help/", views.BuildTranslatedDocsView.as_view(), 
name="action-build-help"),
 ]
diff --git a/vertimus/views.py b/vertimus/views.py
index b39dfaa8..6d3af856 100644
--- a/vertimus/views.py
+++ b/vertimus/views.py
@@ -10,9 +10,12 @@ from xml.dom.minidom import parse
 from django.conf import settings
 from django.contrib import messages
 from django.http import (
-    Http404, HttpResponseRedirect, HttpResponseForbidden, StreamingHttpResponse,
+    Http404,
+    HttpResponseForbidden,
+    HttpResponseRedirect,
+    StreamingHttpResponse,
 )
-from django.shortcuts import render, get_object_or_404
+from django.shortcuts import get_object_or_404, render
 from django.urls import reverse
 from django.utils.html import escape
 from django.utils.safestring import mark_safe
@@ -20,11 +23,22 @@ from django.utils.translation import gettext as _
 from django.views.generic import View
 
 from stats.models import (
-    Statistics, FakeLangStatistics, Module, ModuleLock, Branch, Domain, Language,
+    Branch,
+    Domain,
+    FakeLangStatistics,
+    Language,
+    Module,
+    ModuleLock,
+    Statistics,
+)
+from stats.utils import (
+    DocFormat,
+    UndetectableDocFormat,
+    check_po_quality,
+    is_po_reduced,
 )
-from stats.utils import DocFormat, UndetectableDocFormat, check_po_quality, is_po_reduced
-from vertimus.models import State, Action, ActionArchived, SendMailFailed
 from vertimus.forms import ActionForm
+from vertimus.models import Action, ActionArchived, SendMailFailed, State
 
 
 def vertimus_by_stats_id(request, stats_id, lang_id):
@@ -42,13 +56,10 @@ def vertimus_by_ids(request, branch_id, domain_id, language_id):
     return vertimus(request, branch, domain, language)
 
 
-def vertimus_by_names(request, module_name, branch_name,
-                      domain_name, locale_name, level="0"):
+def vertimus_by_names(request, module_name, branch_name, domain_name, locale_name, level="0"):
     """Access to Vertimus view by Branch, Domain and Language names"""
     module = get_object_or_404(Module, name=module_name)
-    branch = get_object_or_404(
-        Branch.objects.select_related('module'), name=branch_name, module__id=module.id
-    )
+    branch = get_object_or_404(Branch.objects.select_related("module"), name=branch_name, 
module__id=module.id)
     try:
         domain = branch.get_domains()[domain_name]
     except KeyError:
@@ -59,17 +70,19 @@ def vertimus_by_names(request, module_name, branch_name,
 
 def vertimus(request, branch, domain, language, stats=None, level="0"):
     """The Vertimus view and form management. Level argument is used to
-       access to the previous action history, first level (1) is the
-       grandparent, second (2) is the parent of the grandparent, etc."""
+    access to the previous action history, first level (1) is the
+    grandparent, second (2) is the parent of the grandparent, etc."""
     level = int(level)
 
     pot_stats, stats, state = get_vertimus_state(branch, domain, language, stats=stats)
     # Filtering on domain.name instead of domain because we can have several domains
     # working on the same set of strings (e.g. when an extraction method changed,
     # each extraction is mapped to a different domain with branch_from/branch_to delimitations)
-    other_branch_states = State.objects.filter(
-        branch__module=branch.module, domain__name=domain.name, language=language
-    ).exclude(branch=branch.pk).exclude(name='None')
+    other_branch_states = (
+        State.objects.filter(branch__module=branch.module, domain__name=domain.name, language=language)
+        .exclude(branch=branch.pk)
+        .exclude(name="None")
+    )
 
     if level == 0:
         # Current actions
@@ -90,66 +103,53 @@ def vertimus(request, branch, domain, language, stats=None, level="0"):
         # possible to edit an archived workflow
         available_actions = state.get_available_actions(person)
         has_ml = bool(language.team.mailing_list) if language.team else False
-        if request.method == 'POST':
-            action_form = ActionForm(
-                request.user, state, available_actions, has_ml, request.POST, request.FILES
-            )
+        if request.method == "POST":
+            action_form = ActionForm(request.user, state, available_actions, has_ml, request.POST, 
request.FILES)
 
             if action_form.is_valid():
                 # Process the data in form.cleaned_data
-                action = action_form.cleaned_data['action']
+                action = action_form.cleaned_data["action"]
 
-                action = Action.new_by_name(
-                    action, person=person, file=request.FILES.get('file', None)
-                )
+                action = Action.new_by_name(action, person=person, file=request.FILES.get("file", None))
                 try:
                     msg = action.apply_on(state, action_form.cleaned_data)
                 except SendMailFailed:
-                    messages.error(
-                        request,
-                        _("A problem occurred while sending mail, no mail have been sent")
-                    )
+                    messages.error(request, _("A problem occurred while sending mail, no mail have been 
sent"))
                 except Exception as e:
-                    messages.error(
-                        request,
-                        _("An error occurred during applying your action: %s") % e
-                    )
+                    messages.error(request, _("An error occurred during applying your action: %s") % e)
                 else:
                     if msg:
                         messages.success(request, msg)
 
                 return HttpResponseRedirect(
-                    reverse(
-                        'vertimus_by_names',
-                        args=(branch.module.name, branch.name, domain.name, language.locale)
-                    )
+                    reverse("vertimus_by_names", args=(branch.module.name, branch.name, domain.name, 
language.locale))
                 )
         elif available_actions:
             action_form = ActionForm(request.user, state, available_actions, has_ml)
 
     context = {
-        'pageSection': 'module',
-        'stats': stats,
-        'pot_stats': pot_stats,
-        'po_url': stats.po_url(),
-        'po_url_reduced': stats.has_reducedstat() and stats.po_url(reduced=True) or '',
-        'branch': branch,
-        'other_states': other_branch_states,
-        'domain': domain,
-        'language': language,
-        'module': branch.module,
-        'non_standard_repo_msg': _(settings.VCS_HOME_WARNING),
-        'state': state,
-        'is_team_member': person and language.team and person.is_translator(language.team),
-        'action_history': action_history,
-        'action_form': action_form,
-        'level': level,
-        'grandparent_level': grandparent_level,
+        "pageSection": "module",
+        "stats": stats,
+        "pot_stats": pot_stats,
+        "po_url": stats.po_url(),
+        "po_url_reduced": stats.has_reducedstat() and stats.po_url(reduced=True) or "",
+        "branch": branch,
+        "other_states": other_branch_states,
+        "domain": domain,
+        "language": language,
+        "module": branch.module,
+        "non_standard_repo_msg": _(settings.VCS_HOME_WARNING),
+        "state": state,
+        "is_team_member": person and language.team and person.is_translator(language.team),
+        "action_history": action_history,
+        "action_form": action_form,
+        "level": level,
+        "grandparent_level": grandparent_level,
     }
     if stats.has_figures():
-        context['fig_stats'] = stats.fig_stats()
-        del context['fig_stats']['prc']
-    return render(request, 'vertimus/vertimus_detail.html', context)
+        context["fig_stats"] = stats.fig_stats()
+        del context["fig_stats"]["prc"]
+    return render(request, "vertimus/vertimus_detail.html", context)
 
 
 def get_vertimus_state(branch, domain, language, stats=None):
@@ -184,9 +184,9 @@ def vertimus_diff(request, action_id_1, action_id_2, level):
         raise Http404("File not found")
 
     descr_1 = _('<a href="%(url)s">Uploaded file</a> by %(name)s on %(date)s') % {
-        'url': action_1.most_uptodate_file.url,
-        'name': action_1.person_name,
-        'date': action_1.created
+        "url": action_1.most_uptodate_file.url,
+        "name": action_1.person_name,
+        "date": action_1.created,
     }
 
     if action_id_2 not in (None, 0):
@@ -194,9 +194,9 @@ def vertimus_diff(request, action_id_1, action_id_2, level):
         action_2 = get_object_or_404(action_real, pk=action_id_2)
         file_path_2 = action_2.most_uptodate_filepath
         descr_2 = _('<a href="%(url)s">Uploaded file</a> by %(name)s on %(date)s') % {
-            'url': action_2.most_uptodate_file.url,
-            'name': action_2.person_name,
-            'date': action_2.created,
+            "url": action_2.most_uptodate_file.url,
+            "name": action_2.person_name,
+            "date": action_2.created,
         }
     else:
         action_2 = None
@@ -207,52 +207,49 @@ def vertimus_diff(request, action_id_1, action_id_2, level):
         if action_2:
             file_path_2 = action_2.most_uptodate_filepath
             descr_2 = _('<a href="%(url)s">Uploaded file</a> by %(name)s on %(date)s') % {
-                'url': action_2.most_uptodate_file.url,
-                'name': action_2.person_name,
-                'date': action_2.created,
+                "url": action_2.most_uptodate_file.url,
+                "name": action_2.person_name,
+                "date": action_2.created,
             }
         else:
             # 3) Lastly, the file should be the more recently committed file (merged)
             try:
                 stats = Statistics.objects.get(branch=state.branch, domain=state.domain, 
language=state.language)
                 descr_2 = _('<a href="%(url)s">Latest committed file</a> for %(lang)s') % {
-                    'url': stats.po_url(),
-                    'lang': state.language.get_name(),
+                    "url": stats.po_url(),
+                    "lang": state.language.get_name(),
                 }
             except Statistics.DoesNotExist:
                 stats = get_object_or_404(Statistics, branch=state.branch, domain=state.domain, 
language=None)
                 descr_2 = '<a href="%(url)s">%(text)s</a>' % {
-                    'url': stats.pot_url(),
-                    'text': _("Latest POT file"),
+                    "url": stats.pot_url(),
+                    "text": _("Latest POT file"),
                 }
             file_path_2 = stats.po_path(reduced=reduced)
     if not os.path.exists(file_path_2):
         raise Http404("File not found")
 
     d = difflib.HtmlDiff(wrapcolumn=80)
-    with open(file_path_1, encoding='utf-8', errors='replace') as fh1, \
-            open(file_path_2, encoding='utf-8', errors='replace') as fh2:
-        diff_content = d.make_table(
-            fh2.readlines(), fh1.readlines(),
-            descr_2, descr_1, context=True
-        )
+    with open(file_path_1, encoding="utf-8", errors="replace") as fh1, open(
+        file_path_2, encoding="utf-8", errors="replace"
+    ) as fh2:
+        diff_content = d.make_table(fh2.readlines(), fh1.readlines(), descr_2, descr_1, context=True)
 
     context = {
-        'diff_content': diff_content,
-        'state': state,
+        "diff_content": diff_content,
+        "state": state,
     }
-    return render(request, 'vertimus/vertimus_diff.html', context)
+    return render(request, "vertimus/vertimus_diff.html", context)
 
 
 def latest_uploaded_po(request, module_name, branch_name, domain_name, locale_name):
-    """ Redirect to the latest uploaded po for a module/branch/language """
+    """Redirect to the latest uploaded po for a module/branch/language"""
     branch = get_object_or_404(Branch, module__name=module_name, name=branch_name)
     domain = get_object_or_404(Domain, module__name=module_name, name=domain_name)
     lang = get_object_or_404(Language, locale=locale_name)
-    latest_upload = Action.objects.filter(state_db__branch=branch,
-                                          state_db__domain=domain,
-                                          state_db__language=lang,
-                                          file__endswith=".po").order_by('-created')[:1]
+    latest_upload = Action.objects.filter(
+        state_db__branch=branch, state_db__domain=domain, state_db__language=lang, file__endswith=".po"
+    ).order_by("-created")[:1]
     if not latest_upload:
         raise Http404
     return HttpResponseRedirect(latest_upload[0].merged_file.url)
@@ -260,13 +257,13 @@ def latest_uploaded_po(request, module_name, branch_name, domain_name, locale_na
 
 def activity_by_language(request, locale):
     language = get_object_or_404(Language, locale=locale)
-    states = State.objects.filter(language=language).exclude(name='None')
+    states = State.objects.filter(language=language).exclude(name="None")
     context = {
-        'pageSection': "languages",
-        'language': language,
-        'activities': states,
+        "pageSection": "languages",
+        "language": language,
+        "activities": states,
     }
-    return render(request, 'vertimus/activity_summary.html', context)
+    return render(request, "vertimus/activity_summary.html", context)
 
 
 class PoFileActionBase(View):
@@ -277,12 +274,12 @@ class PoFileActionBase(View):
 
     def get_po_file(self):
         pofile = None
-        if self.kwargs.get('action_pk'):
-            self.action = get_object_or_404(Action, pk=self.kwargs['action_pk'])
+        if self.kwargs.get("action_pk"):
+            self.action = get_object_or_404(Action, pk=self.kwargs["action_pk"])
             if self.action.has_po_file():
                 pofile = self.action.most_uptodate_filepath
-        elif self.kwargs.get('stats_pk'):
-            stats = get_object_or_404(Statistics, pk=self.kwargs['stats_pk'])
+        elif self.kwargs.get("stats_pk"):
+            stats = get_object_or_404(Statistics, pk=self.kwargs["stats_pk"])
             pofile = stats.po_path()
         else:
             raise Http404("action_pk and stats_pk are both None")
@@ -290,30 +287,32 @@ class PoFileActionBase(View):
 
 
 class QualityCheckView(PoFileActionBase):
-    template_name = 'vertimus/quality-check.html'
+    template_name = "vertimus/quality-check.html"
 
     def get_context_data(self, **kwargs):
-        context = {'base': 'base_modal.html'}
+        context = {"base": "base_modal.html"}
         if self.pofile is None:
-            context['results'] = _('No po file to check')
+            context["results"] = _("No po file to check")
         else:
-            context['checks'] = ['xmltags']
+            context["checks"] = ["xmltags"]
             try:
-                results = check_po_quality(self.pofile, context['checks'])
+                results = check_po_quality(self.pofile, context["checks"])
             except OSError as err:
-                context['results'] = 'Sorry, the server was not able to run the quality checks on this file 
(%s)' % err
+                context["results"] = "Sorry, the server was not able to run the quality checks on this file 
(%s)" % err
             else:
                 if results:
-                    context['results'] = mark_safe(re.sub(
-                        r'^(# \(pofilter\) .*)', r'<span class="highlight">\1</span>', escape(results), 
flags=re.M
-                    ))
+                    context["results"] = mark_safe(
+                        re.sub(
+                            r"^(# \(pofilter\) .*)", r'<span class="highlight">\1</span>', escape(results), 
flags=re.M
+                        )
+                    )
                 else:
-                    context['results'] = _('The po file looks good!')
+                    context["results"] = _("The po file looks good!")
         return context
 
 
 class MsgiddiffView(PoFileActionBase):
-    HEADER = '''
+    HEADER = """
 <!DOCTYPE html>
 <html>
     <head>
@@ -333,47 +332,47 @@ class MsgiddiffView(PoFileActionBase):
         </style>
     </head>
 <body>
-''' + '<div class="warning">{}</div>'.format(
+""" + '<div class="warning">{}</div>'.format(
         _(
             "WARNING: This file is <b>NOT</b> suitable as a base for completing this translation. "
             "It contains HTML markup to highlight differential parts of changed strings."
         )
     )
-    FOOTER = '</body</html>'
+    FOOTER = "</body</html>"
 
     def streamed_file(self, po_file):
         def strip(line):
-            if line.startswith('#| '):
+            if line.startswith("#| "):
                 line = line[3:]
-            if line.startswith('msgid '):
+            if line.startswith("msgid "):
                 line = line[6:]
-            return line.rstrip('\n').strip('"')
+            return line.rstrip("\n").strip('"')
 
         def line_fmt(no, line):
-            return '<span class="noline">%d</span> ' % no + html.escape(line) + '<br>'
+            return '<span class="noline">%d</span> ' % no + html.escape(line) + "<br>"
 
         yield self.HEADER
         prev_id = curr_id = None
         no_wrap = False
         stored_comments = []
         in_fuzzy = False
-        with open(po_file, 'r') as fh:
+        with open(po_file, "r") as fh:
             for noline, line in enumerate(fh.readlines(), start=1):
-                if line.startswith(('#.', '#:')):
+                if line.startswith(("#.", "#:")):
                     stored_comments.append((noline, line))
                     continue
 
-                if line == '\n':
+                if line == "\n":
                     in_fuzzy = False
                     stored_comments = []
                     continue
 
-                if line.startswith('#, fuzzy'):
+                if line.startswith("#, fuzzy"):
                     in_fuzzy = True
                     diff_printed = False
                     prev_id = []
                     curr_id = []
-                    no_wrap = 'no-wrap' in line
+                    no_wrap = "no-wrap" in line
                     yield '<div class="separator">(…)</div>\n'
                     for no, com in stored_comments:
                         yield line_fmt(no, com)
@@ -381,17 +380,19 @@ class MsgiddiffView(PoFileActionBase):
                     continue
 
                 if in_fuzzy:
-                    if line.startswith('#|'):
+                    if line.startswith("#|"):
                         prev_id.append(line)
                         continue
                     if line.startswith('msgstr "'):
                         # Compute and display inline diff
-                        sep = '\n' if no_wrap else ''
+                        sep = "\n" if no_wrap else ""
                         yield (
-                            '<div class="diff%s">' % (' nowrap' if no_wrap else '') + diff_strings(
+                            '<div class="diff%s">' % (" nowrap" if no_wrap else "")
+                            + diff_strings(
                                 html.escape(sep.join(strip(_line) for _line in prev_id)),
-                                html.escape(sep.join(strip(_line) for _, _line in curr_id))
-                            ) + '</div>'
+                                html.escape(sep.join(strip(_line) for _, _line in curr_id)),
+                            )
+                            + "</div>"
                         )
                         for no, _line in curr_id:
                             yield line_fmt(no, _line)
@@ -404,20 +405,20 @@ class MsgiddiffView(PoFileActionBase):
         yield self.FOOTER
 
     def get(self, request, *args, **kwargs):
-        stats = get_object_or_404(Statistics, pk=self.kwargs['stats_pk'])
+        stats = get_object_or_404(Statistics, pk=self.kwargs["stats_pk"])
         pofile = stats.po_path()
         return StreamingHttpResponse(self.streamed_file(pofile))
 
 
 class BuildTranslatedDocsView(PoFileActionBase):
-    http_method_names = ['post']
+    http_method_names = ["post"]
 
     def post(self, request, *args, **kwargs):
         pofile = self.get_po_file()
         if pofile is None:
-            raise Http404('No target po file for this action')
+            raise Http404("No target po file for this action")
 
-        html_dir = settings.SCRATCHDIR / 'HTML' / str(self.kwargs['action_pk'])
+        html_dir = settings.SCRATCHDIR / "HTML" / str(self.kwargs["action_pk"])
         if html_dir.exists():
             # If the build already ran, redirect to the static results
             return HttpResponseRedirect(self.action.build_url)
@@ -425,7 +426,7 @@ class BuildTranslatedDocsView(PoFileActionBase):
         state = self.action.state_db
         team = state.language.team
         if not request.user.is_authenticated or not team or not request.user.person.is_translator(team):
-            return HttpResponseForbidden('Only team members can build docs.')
+            return HttpResponseForbidden("Only team members can build docs.")
 
         with ModuleLock(state.branch.module):
             state.branch.checkout()
@@ -446,63 +447,66 @@ class BuildTranslatedDocsView(PoFileActionBase):
         except UndetectableDocFormat as err:
             return str(err)
 
-        build_error = _('Build failed (%(program)s): %(err)s')
-        with tempfile.NamedTemporaryFile(suffix='.gmo') as gmo, \
-                tempfile.TemporaryDirectory() as build_dir:
-            result = subprocess.run([
-                'msgfmt', po_file, '-o', os.path.join(gmo.name)
-            ], stderr=subprocess.PIPE)
+        build_error = _("Build failed (%(program)s): %(err)s")
+        with tempfile.NamedTemporaryFile(suffix=".gmo") as gmo, tempfile.TemporaryDirectory() as build_dir:
+            result = subprocess.run(["msgfmt", po_file, "-o", os.path.join(gmo.name)], 
stderr=subprocess.PIPE)
             if result.returncode != 0:
                 return build_error % {
-                    'program': 'msgfmt',
-                    'err': result.stderr.decode(errors='replace'),
+                    "program": "msgfmt",
+                    "err": result.stderr.decode(errors="replace"),
                 }
 
             sources = doc_format.source_files()
-            result = subprocess.run([
-                'itstool', '-m', gmo.name, '-l', state.language.locale,
-                '-o', str(build_dir), '--strict',
-                *[str(s) for s in sources],
-            ], cwd=str(doc_format.vcs_path), stderr=subprocess.PIPE)
+            result = subprocess.run(
+                [
+                    "itstool",
+                    "-m",
+                    gmo.name,
+                    "-l",
+                    state.language.locale,
+                    "-o",
+                    str(build_dir),
+                    "--strict",
+                    *[str(s) for s in sources],
+                ],
+                cwd=str(doc_format.vcs_path),
+                stderr=subprocess.PIPE,
+            )
             if result.returncode != 0:
                 return build_error % {
-                    'program': 'itstool',
-                    'err': result.stderr.decode(errors='replace'),
+                    "program": "itstool",
+                    "err": result.stderr.decode(errors="replace"),
                 }
 
             # Now build the html version
             if not html_dir.exists():
                 html_dir.mkdir(parents=True)
-            if doc_format.format == 'mallard':
+            if doc_format.format == "mallard":
                 # With mallard, specifying the directory is enough.
                 build_ref = [str(build_dir)]
             else:
                 build_ref = [os.path.join(build_dir, s.name) for s in sources]
-            cmd = [
-                'yelp-build', 'html', '-o', str(html_dir),
-                '-p', str(doc_format.vcs_path / 'C'),
-                *build_ref
-            ]
+            cmd = ["yelp-build", "html", "-o", str(html_dir), "-p", str(doc_format.vcs_path / "C"), 
*build_ref]
             result = subprocess.run(cmd, cwd=str(build_dir), stderr=subprocess.PIPE)
-            index_html = html_dir / 'index.html'
+            index_html = html_dir / "index.html"
             if result.returncode != 0 or (not index_html.exists() and len(result.stderr) > 0):
                 shutil.rmtree(str(html_dir))
                 return build_error % {
-                    'program': 'yelp-build',
-                    'err': result.stderr.decode(errors='replace'),
+                    "program": "yelp-build",
+                    "err": result.stderr.decode(errors="replace"),
                 }
 
             if not index_html.exists() and os.path.isfile(build_ref[0]):
                 # Create an index.html symlink to the base html doc if needed
                 try:
                     doc = parse(build_ref[0])
-                    base_name = doc.getElementsByTagName("article")[0].attributes.get('id').value
+                    base_name = doc.getElementsByTagName("article")[0].attributes.get("id").value
                 except (AttributeError, IndexError):
                     pass
                 else:
-                    html_name = '%s.html' % base_name
-                    (html_dir / 'index.html').symlink_to(html_dir / html_name)
-        return ''
+                    html_name = "%s.html" % base_name
+                    (html_dir / "index.html").symlink_to(html_dir / html_name)
+        return ""
 
 
 def diff_strings(previous, current):
@@ -513,22 +517,22 @@ def diff_strings(previous, current):
     'lorem<ins> foo</ins> ipsum dolor <del>sit </del>amet'
     """
     if not previous or not current:
-        return ''
+        return ""
     seqm = difflib.SequenceMatcher(
-        a=previous.replace('\r\n', '\n'),
-        b=current.replace('\r\n', '\n'),
+        a=previous.replace("\r\n", "\n"),
+        b=current.replace("\r\n", "\n"),
     )
     output = []
     for opcode, a0, a1, b0, b1 in seqm.get_opcodes():
-        if opcode == 'equal':
+        if opcode == "equal":
             output.append(seqm.a[a0:a1])
-        elif opcode == 'insert':
+        elif opcode == "insert":
             output.append('<ins title="New text">' + seqm.b[b0:b1] + "</ins>")
-        elif opcode == 'delete':
+        elif opcode == "delete":
             output.append('<del title="Deleted text">' + seqm.a[a0:a1] + "</del>")
-        elif opcode == 'replace':
+        elif opcode == "replace":
             output.append('<del title="Deleted text">' + seqm.a[a0:a1] + "</del>")
             output.append('<ins title="New text">' + seqm.b[b0:b1] + "</ins>")
         else:
             raise RuntimeError("unexpected opcode")
-    return mark_safe(''.join(output))
+    return mark_safe("".join(output))


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