[damned-lies] chore: flake8 fixes on stats



commit 0926bca33e3e33a6dd9fdeb77f3163f045517450
Author: Guillaume Bernard <associations guillaume-bernard fr>
Date:   Sat May 1 16:53:14 2021 +0200

    chore: flake8 fixes on stats

 stats/admin.py                                |  23 +-
 stats/forms.py                                |   9 +-
 stats/management/commands/run-maintenance.py  |   1 +
 stats/management/commands/update-from-doap.py |   1 +
 stats/management/commands/update-stats.py     |  26 +-
 stats/management/commands/update-trans.py     |   7 +-
 stats/models.py                               | 354 +++++++++++++++-----------
 stats/potdiff.py                              |  36 ++-
 stats/repos.py                                |   6 +-
 stats/templatetags/stats_extras.py            |   4 +-
 stats/tests/fixture_factory.py                | 192 +++++++++-----
 stats/tests/tests.py                          | 110 ++++----
 stats/tests/utils.py                          |  10 +-
 stats/utils.py                                | 123 +++++----
 stats/views.py                                |  51 ++--
 vertimus/tests/tests.py                       |   4 +-
 16 files changed, 586 insertions(+), 371 deletions(-)
---
diff --git a/stats/admin.py b/stats/admin.py
index 0f334446..466766fb 100644
--- a/stats/admin.py
+++ b/stats/admin.py
@@ -63,11 +63,11 @@ class DomainInline(admin.StackedInline):
 
     def formfield_for_dbfield(self, db_field, **kwargs):
         if db_field.name == 'description':
-            kwargs['widget'] = forms.Textarea(attrs={'rows':'1', 'cols':'20'})
+            kwargs['widget'] = forms.Textarea(attrs={'rows': '1', 'cols': '20'})
         elif db_field.name in ('name', 'layout'):
-            kwargs['widget'] = forms.TextInput(attrs={'size':'20'})
+            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'})
+            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):
@@ -100,14 +100,14 @@ class ModuleAdmin(admin.ModelAdmin):
     form = ModuleForm
     fieldsets = (
         (None, {
-            'fields': (('name','description'),
-                        'homepage', 'comment', 'archived',
+            'fields': (('name', 'description'),
+                       'homepage', 'comment', 'archived',
                        ('bugs_base',),
                        ('vcs_type', 'vcs_root', 'vcs_web'),
                        'ext_platform', 'maintainers')
         }),
     )
-    inlines = [ BranchInline, DomainInline ]
+    inlines = [BranchInline, DomainInline]
     search_fields = ('name', 'homepage', 'comment', 'vcs_web')
     list_filter = ('archived',)
 
@@ -136,18 +136,21 @@ class DomainAdmin(admin.ModelAdmin):
     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')
 
+
 class ReleaseAdmin(admin.ModelAdmin):
     list_display = ('name', 'status', 'weight', 'string_frozen')
     list_editable = ('weight',)
-    inlines = [ CategoryInline ]
+    inlines = [CategoryInline]
     actions = ['copy_release', 'delete_release']
 
     def copy_release(self, request, queryset):
@@ -178,6 +181,7 @@ class ReleaseAdmin(admin.ModelAdmin):
                 branch_seen.add(branch)
         messages.success(request, "New release '%s' created" % new_rel.name)
         return HttpResponseRedirect(request.path)
+
     copy_release.short_description = "Copy release (and associated branches)"
 
     def delete_release(self, request, queryset):
@@ -210,6 +214,7 @@ class ReleaseAdmin(admin.ModelAdmin):
             "action_checkbox_name": helpers.ACTION_CHECKBOX_NAME,
         }
         return render(request, 'admin/delete_release_confirmation.html', context)
+
     delete_release.short_description = "Delete release (and associated branches)"
 
 
@@ -221,7 +226,7 @@ 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')
-    inlines = [ InformationInline ]
+    inlines = [InformationInline]
 
 
 class PoFileAdmin(admin.ModelAdmin):
diff --git a/stats/forms.py b/stats/forms.py
index fbdac4e0..d0190ef6 100644
--- a/stats/forms.py
+++ b/stats/forms.py
@@ -26,7 +26,7 @@ class ModuleBranchForm(forms.Form):
                     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(
+                    self.fields[str(category.id) + '_cat'] = forms.ModelChoiceField(
                         queryset=CategoryName.objects.all(),
                         required=False,
                         initial=category.name
@@ -58,8 +58,11 @@ class ModuleBranchForm(forms.Form):
     def clean(self):
         cleaned_data = super().clean()
         for field_name in list(cleaned_data.keys()):
-            if (field_name.endswith('_cat') and cleaned_data[field_name] is None
-                    and cleaned_data[field_name[:-4]] is not None):
+            if (
+                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."))
diff --git a/stats/management/commands/run-maintenance.py b/stats/management/commands/run-maintenance.py
index 994a47bb..24825ac9 100644
--- a/stats/management/commands/run-maintenance.py
+++ b/stats/management/commands/run-maintenance.py
@@ -5,6 +5,7 @@ from teams.models import Role
 from vertimus.models import ActionArchived
 from languages.views import clean_tar_files
 
+
 class Command(BaseCommand):
     help = "Run maintenance tasks"
 
diff --git a/stats/management/commands/update-from-doap.py b/stats/management/commands/update-from-doap.py
index 8f3e2984..00a47097 100644
--- a/stats/management/commands/update-from-doap.py
+++ b/stats/management/commands/update-from-doap.py
@@ -4,6 +4,7 @@ from django.core.management.base import BaseCommand
 from stats.models import Module, ModuleLock
 from stats.doap import update_doap_infos
 
+
 class Command(BaseCommand):
     help = "Update module information from doap file"
 
diff --git a/stats/management/commands/update-stats.py b/stats/management/commands/update-stats.py
index b5ae9969..fcd241c1 100644
--- a/stats/management/commands/update-stats.py
+++ b/stats/management/commands/update-stats.py
@@ -13,18 +13,26 @@ class Command(BaseCommand):
     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('--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")
+        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(
+            '--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']:
-            import pdb; pdb.set_trace()
+            breakpoint()
         self.force_stats = options['force']
         if options['module'] and options['branch']:
             # Update the specific branch(es) of a module
diff --git a/stats/management/commands/update-trans.py b/stats/management/commands/update-trans.py
index 2f92c7d0..7ec9f941 100644
--- a/stats/management/commands/update-trans.py
+++ b/stats/management/commands/update-trans.py
@@ -1,16 +1,16 @@
+import itertools
 import os
 import re
 import shutil
-import itertools
 
 from django.conf import settings
 from django.core.management import call_command
 from django.core.management.base import BaseCommand
 from django.db.models import F
 
-from teams.models import Team
 from languages.models import Language
 from stats.models import Module, Domain, Release, CategoryName
+from teams.models import Team
 
 
 class Command(BaseCommand):
@@ -46,7 +46,8 @@ class Command(BaseCommand):
                 Module.objects.exclude(name__exact=F('description')).values_list('description', flat=True),
                 Module.objects.exclude(comment='').values_list('comment', flat=True),
                 Release.objects.values_list('description', flat=True),
-                CategoryName.objects.values_list('name', flat=True)):
+                CategoryName.objects.values_list('name', flat=True)
+            ):
                 if value:
                     value = re.sub(r'\r\n|\r|\n', '\n', value)
                     fh.write("_(u\"\"\"%s\"\"\")\n" % value)
diff --git a/stats/models.py b/stats/models.py
index 8505b5de..54612d7a 100644
--- a/stats/models.py
+++ b/stats/models.py
@@ -56,7 +56,6 @@ GLIB_PRESET = (
     '--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')
 validate_slug = RegexValidator(
@@ -87,8 +86,10 @@ 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,13 +97,19 @@ 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', blank=True,
-        help_text="Automatically updated if the module contains a doap file.")
+    maintainers = models.ManyToManyField(
+        Person,
+        db_table='module_maintainer',
+        related_name='maintains_modules',
+        blank=True,
+        help_text="Automatically updated if the module contains a doap file."
+    )
 
     class Meta:
         db_table = 'module'
@@ -129,9 +136,10 @@ class Module(models.Model):
                 comment += "<br/>"
             comment = "%s<em>%s</em>" % (
                 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
-                }
+                _(
+                    '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}
             )
         return comment
 
@@ -166,7 +174,7 @@ class Module(models.Model):
 
     def can_edit_branches(self, user):
         """ 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() ]:
+        if is_site_admin(user) or user.username in [p.username for p in self.maintainers.all()]:
             return True
         return False
 
@@ -185,6 +193,7 @@ 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
     """
+
     def __init__(self, mod):
         self.module = mod
         self.dirpath = settings.LOCK_DIR / f"updating-{self.module.name}"
@@ -213,11 +222,10 @@ class ModuleLock:
 @total_ordering
 class Branch(models.Model):
     """ Branch of a module """
-    name        = models.CharField(max_length=50)
-    #description = models.TextField(null=True)
+    name = models.CharField(max_length=50)
     vcs_subpath = models.CharField(max_length=50, blank=True)
-    module      = models.ForeignKey(Module, on_delete=models.CASCADE)
-    weight      = models.IntegerField(default=0, help_text="Smaller weight is displayed first")
+    module = models.ForeignKey(Module, on_delete=models.CASCADE)
+    weight = models.IntegerField(default=0, help_text="Smaller weight is displayed first")
     file_hashes = models.JSONField(blank=True, null=True, editable=False)
     # 'releases' is the backward relation name from Release model
 
@@ -254,13 +262,15 @@ class Branch(models.Model):
                 with ModuleLock(self.module):
                     self.checkout()
             except Exception:
-                raise ValidationError("Branch not valid: error while checking out the branch (%s)." % 
sys.exc_info()[1])
+                raise ValidationError(
+                    "Branch not valid: error while checking out the branch (%s)." % sys.exc_info()[1]
+                )
 
     def save(self, update_statistics=True, **kwargs):
         super().save(**kwargs)
         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):
@@ -312,7 +322,7 @@ class Branch(models.Model):
         """
         full_path = self.co_path / rel_path
         if not full_path.exists():
-            return False # Raise exception?
+            return False  # FIXME: Raise exception?
         new_hash = utils.compute_md5(full_path)
         if self.file_hashes and self.file_hashes.get(rel_path, None) == new_hash:
             return False
@@ -371,7 +381,7 @@ class Branch(models.Model):
 
     def has_domain(self, domain):
         # generate query only if branch_from/branch_to are defined.
-        if (domain.branch_from_id or domain.branch_to_id):
+        if domain.branch_from_id or domain.branch_to_id:
             if (domain.branch_from and self > domain.branch_from) or (domain.branch_to and self < 
domain.branch_to):
                 return False
         return True
@@ -387,22 +397,28 @@ class Branch(models.Model):
         return dirname
 
     def get_stats(self, typ, mandatory_langs=()):
-        """ Get statistics list of type typ ('ui' or 'doc'), in a dict of lists, key is domain.name (POT in 
1st position)
-            stats = {'po':      [potstat, polang1, polang2, ...],
-                     'po-tips': [potstat, polang1, polang2, ...]}
-            mandatory_langs is an iterable of language objects whose stats should be added even if no 
translation exists.
+        """
+        Get statistics list of type typ ('ui' or 'doc'), in a dict of lists, key is domain.name (POT in 1st 
position)
+        stats = {'po':      [potstat, polang1, polang2, ...],
+                 'po-tips': [potstat, polang1, polang2, ...]}
+        mandatory_langs is an iterable of language objects whose stats should be added even if no 
translation exists.
         """
         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[stat.domain.name] = [stat]
             stats_langs[stat.domain.name] = []
-        tr_stats = Statistics.objects.select_related("language", "domain", "branch", "full_po"
-                        ).filter(branch=self, language__isnull=False, domain__pk__in=domain_pks)
+        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():
             stats[stat.domain.name].append(stat)
             stats_langs[stat.domain.name].append(stat.language)
@@ -414,7 +430,7 @@ class Branch(models.Model):
         # Sort
         for doms in stats.values():
             # Sort stats, pot first, then translated (desc), then language name
-            doms.sort(key=lambda st:(int(st.language is not None), -st.translated(), st.get_lang()))
+            doms.sort(key=lambda st: (int(st.language is not None), -st.translated(), st.get_lang()))
         return stats
 
     def get_doc_stats(self, mandatory_langs=()):
@@ -653,14 +669,16 @@ class Domain(models.Model):
     )
     linguas_location = models.CharField(
         max_length=50, blank=True,
-        help_text=f"Use 'no' for no LINGUAS check, or path/to/file#variable for a non-standard location.\n"
-                  f"Leave blank for standard location (ALL_LINGUAS in LINGUAS/configure.ac/.in for UI and 
DOC_LINGUAS "
-                  f"in Makefile.am for DOC)"
+        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=f"pogrep filter to strip po file from unprioritized strings (format: location|string,"
-                  f" “-” for no filter)"
+        help_text="pogrep filter to strip po file from unprioritized strings (format: location|string,"
+                  " “-” 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='+')
@@ -804,10 +822,10 @@ class Domain(models.Model):
                 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)
-                    }),
+                         '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
@@ -831,13 +849,14 @@ class Domain(models.Model):
         return potfile, ()
 
     def get_xgettext_command(self, branch):
-        pot_command = ['xgettext',
-                       '--files-from', 'POTFILES.in',
-                       '--directory', str(branch.co_path),
-                       '--from-code', 'utf-8',
-                       '--add-comments',
-                       '--output', '%s.pot' % self.potbase(),
-                      ]
+        pot_command = [
+            'xgettext',
+            '--files-from', 'POTFILES.in',
+            '--directory', str(branch.co_path),
+            '--from-code', 'utf-8',
+            '--add-comments',
+            '--output', '%s.pot' % self.potbase(),
+        ]
         if not os.path.exists(utils.ITS_DATA_DIR):
             utils.collect_its_data()
         env = {'GETTEXTDATADIRS': os.path.dirname(utils.ITS_DATA_DIR)}
@@ -941,7 +960,7 @@ class Release(models.Model):
     string_frozen = models.BooleanField(default=False)
     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)
+    weight = models.IntegerField(default=0)
     branches = models.ManyToManyField(Branch, through='Category', related_name='releases')
 
     class Meta:
@@ -983,12 +1002,16 @@ class Release(models.Model):
         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']
                 if locale and locale not in stats:
@@ -1042,8 +1065,9 @@ class Release(models.Model):
         ).values('branch_id', 'domain_id', 'language__locale', 'part_po__translated',
                  'part_po__fuzzy', 'part_po__untranslated')
         stats_d = {
-            "%d-%d-%s" % (st['branch_id'], st['domain_id'], st['language__locale']
-            ): (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():
@@ -1087,26 +1111,33 @@ class Release(models.Model):
 
         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))
-            )
-        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,
-                        },
-                 '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
-                        },
-                }
+        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,
+            },
+            '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
+            },
+        }
         for res in query:
             if res['domain__dtype'] == 'ui':
                 stats['ui']['translated'] = res['trans']
@@ -1119,34 +1150,37 @@ class Release(models.Model):
         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['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')
+        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()
@@ -1156,12 +1190,18 @@ class Release(models.Model):
                 # 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},
-                    '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,},
+                    '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
+                    },
+                    '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']
@@ -1180,7 +1220,7 @@ class Release(models.Model):
                 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']
+                    row['trans_p'] + row['fuzzy_p']
                 )
                 if total_uistrings > 0:
                     stats[locale]['ui']['translated_perc'] = int(100 * row['trans'] / total_uistrings)
@@ -1207,9 +1247,10 @@ class Release(models.Model):
         """ 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),
-                }
+        stats = {
+            '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):
@@ -1263,16 +1304,16 @@ class Category(models.Model):
 
 class PoFile(models.Model):
     # File type fields of Django may not be flexible enough for our use case
-    path         = models.CharField(max_length=255, blank=True)
-    updated      = models.DateTimeField(auto_now_add=True)
-    translated   = models.IntegerField(default=0)
-    fuzzy        = models.IntegerField(default=0)
+    path = models.CharField(max_length=255, blank=True)
+    updated = models.DateTimeField(auto_now_add=True)
+    translated = models.IntegerField(default=0)
+    fuzzy = models.IntegerField(default=0)
     untranslated = models.IntegerField(default=0)
     # List of figure dict
     figures = models.JSONField(blank=True, null=True)
     # words statistics
-    translated_words   = models.IntegerField(default=0)
-    fuzzy_words        = models.IntegerField(default=0)
+    translated_words = models.IntegerField(default=0)
+    fuzzy_words = models.IntegerField(default=0)
     untranslated_words = models.IntegerField(default=0)
 
     class Meta:
@@ -1343,12 +1384,12 @@ 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.translated = stats['translated']
+        self.fuzzy = stats['fuzzy']
         self.untranslated = stats['untranslated']
 
-        self.translated_words   = stats['translated_words']
-        self.fuzzy_words        = stats['fuzzy_words']
+        self.translated_words = stats['translated_words']
+        self.fuzzy_words = stats['fuzzy_words']
         self.untranslated_words = stats['untranslated_words']
 
         self.save()
@@ -1370,7 +1411,7 @@ class Statistics(models.Model):
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
-        self.partial_po = False # True if part of a multiple po module
+        self.partial_po = False  # True if part of a multiple po module
         self.info_list = []
 
     def __str__(self):
@@ -1379,22 +1420,22 @@ class Statistics(models.Model):
                                        self.branch.name, self.get_lang())
 
     def translated(self, scope='full'):
-        return getattr(scope=='part' and self.part_po or self.full_po, 'translated', 0)
+        return getattr(self.part_po if scope == 'part' else self.full_po, 'translated', 0)
 
     def translated_words(self, scope='full'):
-        return getattr(scope=='part' and self.part_po or self.full_po, 'translated_words', 0)
+        return getattr(self.part_po if scope == 'part' else self.full_po, 'translated_words', 0)
 
     def fuzzy(self, scope='full'):
-        return getattr(scope=='part' and self.part_po or self.full_po, 'fuzzy', 0)
+        return getattr(self.part_po if scope == 'part' else self.full_po, 'fuzzy', 0)
 
     def fuzzy_words(self, scope='full'):
-        return getattr(scope=='part' and self.part_po or self.full_po, 'fuzzy_words', 0)
+        return getattr(self.part_po if scope == 'part' else self.full_po, 'fuzzy_words', 0)
 
     def untranslated(self, scope='full'):
-        return getattr(scope=='part' and self.part_po or self.full_po, 'untranslated', 0)
+        return getattr(self.part_po if scope == 'part' else self.full_po, 'untranslated', 0)
 
     def untranslated_words(self, scope='full'):
-        return getattr(scope=='part' and self.part_po or self.full_po, 'untranslated_words', 0)
+        return getattr(self.part_po if scope == 'part' else self.full_po, 'untranslated_words', 0)
 
     def is_fake(self):
         return False
@@ -1485,17 +1526,19 @@ class Statistics(models.Model):
         # 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}
         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 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"))
+        }
         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}
-            text = _("POT file (%(messages)s — %(words)s, %(figures)s) — %(updated)s") % \
-                              {'messages': msg_text, 'figures': fig_text, 'updated': upd_text, 'words': 
words_text }
+            text = _("POT file (%(messages)s — %(words)s, %(figures)s) — %(updated)s") % {
+                '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 }
+            text = _("POT file (%(messages)s — %(words)s) — %(updated)s") % {
+                'messages': msg_text, 'updated': upd_text, 'words': words_text
+            }
         return text
 
     def has_figures(self):
@@ -1525,16 +1568,18 @@ class Statistics(models.Model):
         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
+                if fig.get('fuzzy', 0):
+                    stats['fuzzy'] += 1
                 else:
-                    if fig.get('translated', 0): stats['translated'] += 1
+                    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']
+            stats['prc'] = 100 * stats['translated'] / stats['total']
         return stats
 
     def vcs_web_path(self):
@@ -1559,8 +1604,9 @@ class Statistics(models.Model):
         subdir = ""
         if self.domain.dtype == "doc":
             subdir = "docs/"
-        return utils.url_join("/POT/", "%s.%s" % (self.module_name, self.branch.name_escaped),
-            subdir, self.filename(potfile, reduced))
+        return utils.url_join(
+            "/POT/", "%s.%s" % (self.module_name, self.branch.name_escaped), subdir, self.filename(potfile, 
reduced)
+        )
 
     def pot_url(self):
         return self.po_url(potfile=True)
@@ -1719,9 +1765,11 @@ class Statistics(models.Model):
             dtype = dtype[:-5]
             scope = "part"
 
-        stats = {'dtype':dtype, 'totaltrans':0, 'totalfuzzy':0, 'totaluntrans':0,
-                 'totaltransperc': 0, 'totalfuzzyperc': 0, 'totaluntransperc': 0,
-                 'categs': OrderedDict(), 'all_errors': []}
+        stats = {
+            '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')
         if release:
@@ -1747,7 +1795,7 @@ class Statistics(models.Model):
         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:
@@ -1801,7 +1849,7 @@ class Statistics(models.Model):
             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]]}
+                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]]
@@ -1821,14 +1869,14 @@ class Statistics(models.Model):
         # 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['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'])
+                categ['cattransperc'] = int(100 * categ['cattrans'] / categ['cattotal'])
             # Sort domains
             for mod in categ['modules'].values():
                 for doms in mod.values():
@@ -1841,6 +1889,7 @@ class Statistics(models.Model):
 class FakeLangStatistics:
     """ Statistics class for a non existing lang stats """
     is_fake = True
+
     def __init__(self, pot_stat, lang):
         self.stat = pot_stat
         self.language = lang
@@ -1871,16 +1920,17 @@ class FakeLangStatistics:
 class FakeSummaryStatistics:
     """ Statistics class that sums up an entire module stats """
     is_fake = True
+
     def __init__(self, module, branch, dtype):
         self.module = module
         self.branch = branch
         self.domain = module.domain_set.filter(dtype=dtype)[0]
         self._translated = 0
-        self._fuzzy      = 0
+        self._fuzzy = 0
         self._untranslated = 0
         self.partial_po = False
         self._translated_words = 0
-        self._fuzzy_words      = 0
+        self._fuzzy_words = 0
         self._untranslated_words = 0
 
     def translated(self, scope=None):
@@ -1902,8 +1952,8 @@ class FakeSummaryStatistics:
         return self._untranslated_words
 
     def trans(self, stat):
-        self._translated   += stat.translated()
-        self._fuzzy        += stat.fuzzy()
+        self._translated += stat.translated()
+        self._fuzzy += stat.fuzzy()
         self._untranslated += stat.untranslated()
         stat.partial_po = True
 
@@ -1923,7 +1973,7 @@ class FakeSummaryStatistics:
     def un_percentage(self, scope='full'):
         if self.pot_size() == 0:
             return 0
-        return int(100*self._untranslated/self.pot_size())
+        return int(100 * self._untranslated / self.pot_size())
 
 
 class StatisticsArchived(models.Model):
@@ -1981,7 +2031,7 @@ 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)
 
@@ -1989,14 +2039,14 @@ class Information(models.Model):
 
         #  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),
+            'long': utils.ellipsize(utils.stripHTML(self.get_description()), 1600),
         }
         return link
 
diff --git a/stats/potdiff.py b/stats/potdiff.py
index 613f8a8f..63875b92 100644
--- a/stats/potdiff.py
+++ b/stats/potdiff.py
@@ -4,6 +4,7 @@
 
 USE_DIFFLIB = 0
 
+
 def diff(pota, potb):
     """Returns a list of differing lines between two text files."""
     with open(pota, "r", encoding="utf-8") as f1:
@@ -24,14 +25,15 @@ def diff(pota, potb):
         result_all = []
         while i < len(res1) and j < len(res2):
             if res1[i] == res2[j]:
-                i+=1; j+=1
+                i += 1
+                j += 1
             elif res1[i] < res2[j]:
                 result_all.append("- " + res1[i])
-                i+=1
+                i += 1
             elif res1[i] > res2[j]:
                 result_all.append("+ " + res2[j])
                 result_add_only.append("+ " + res2[j])
-                j+=1
+                j += 1
         return result_all, result_add_only
     else:
         import difflib
@@ -40,9 +42,8 @@ def diff(pota, potb):
 
         onlydiffs = []
         for line in result:
-            if line[0]!=" ":
+            if line[0] != " ":
                 onlydiffs.append(line)
-                #print line
         return onlydiffs
 
 
@@ -54,10 +55,13 @@ def _parse_contents(contents):
 
          [msgctxt::]msgid[/msgid_plural]"""
 
-    if len(contents) and contents[-1] != "\n": contents += "\n"
+    if len(contents) and contents[-1] != "\n":
+        contents += "\n"
 
     # state machine for parsing PO files
-    msgid = ""; msgctxt = ""; plural = "";
+    msgid = ""
+    msgctxt = ""
+    plural = ""
     in_msgid = in_msgstr = in_msgctxt = in_msgid_plural = 0
 
     result = []
@@ -71,9 +75,11 @@ def _parse_contents(contents):
             if in_msgstr and msgid != "":
                 onemsg = ""
 
-                if msgctxt: onemsg += ('"' + msgctxt + '"::')
+                if msgctxt:
+                    onemsg += ('"' + msgctxt + '"::')
                 onemsg += ('"' + msgid + '"')
-                if plural: onemsg += ('/"' + plural + '"')
+                if plural:
+                    onemsg += ('/"' + plural + '"')
 
                 result.append(onemsg)
 
@@ -81,9 +87,13 @@ def _parse_contents(contents):
                 # Ignore PO header
                 pass
 
-            msgid = ""; msgctxt = ""
-            in_msgid = 0; in_msgstr = 0; in_msgctxt = 0
-            plural = ""; in_msgid_plural = 0
+            msgid = ""
+            msgctxt = ""
+            in_msgid = 0
+            in_msgstr = 0
+            in_msgctxt = 0
+            plural = ""
+            in_msgid_plural = 0
 
         elif line[0] == "\"" and line[-1] == "\"":
             if in_msgid:
@@ -124,6 +134,8 @@ def _parse_contents(contents):
             pass
     return result
 
+
 if __name__ == "__main__":
     import sys
+
     print("\n".join(diff(sys.argv[1], sys.argv[2])))
diff --git a/stats/repos.py b/stats/repos.py
index 94217859..f7fc2bf9 100644
--- a/stats/repos.py
+++ b/stats/repos.py
@@ -152,8 +152,8 @@ class CVSRepo(RepoBase):
     def init_checkout(self):
         run_shell_command([
             'cvs', '-d%s' % self.branch.module.vcs_root, '-z4', 'co',
-            '-d%s' % self.branch.module.name + "." + self.branch.name,
-            '-r%s' % self.branch.name, self.module.name
+                   '-d%s' % self.branch.module.name + "." + self.branch.name,
+                   '-r%s' % self.branch.name, self.module.name
         ], cwd=settings.SCRATCHDIR / self.branch.module.vcs_type)
 
     def update(self):
@@ -166,7 +166,7 @@ class CVSRepo(RepoBase):
 
 class MercurialRepo(RepoBase):
     def exists(self):
-        return self.branch.id != None and os.access(str(self.branch.co_path), os.X_OK | os.W_OK)
+        return self.branch.id is not None and os.access(str(self.branch.co_path), os.X_OK | os.W_OK)
 
     def init_checkout(self):
         base_path = self.branch.co_path
diff --git a/stats/templatetags/stats_extras.py b/stats/templatetags/stats_extras.py
index 9e884088..9e07044a 100644
--- a/stats/templatetags/stats_extras.py
+++ b/stats/templatetags/stats_extras.py
@@ -223,7 +223,9 @@ def markdown(value, arg=''):
         import markdown
     except ImportError:
         if settings.DEBUG:
-            raise template.TemplateSyntaxError("Error in 'markdown' filter: The Python markdown library 
isn't installed.")
+            raise template.TemplateSyntaxError(
+                "Error in 'markdown' filter: The Python markdown library isn't installed."
+            )
         return value
     else:
         extensions = [e for e in arg.split(",") if e]
diff --git a/stats/tests/fixture_factory.py b/stats/tests/fixture_factory.py
index 80ce1ddb..0827de38 100644
--- a/stats/tests/fixture_factory.py
+++ b/stats/tests/fixture_factory.py
@@ -7,79 +7,100 @@ from languages.models import Language
 from teams.models import Team, Role
 from people.models import Person
 from stats.models import (
-    Module, Domain, Branch, Release, Category, CategoryName, Statistics,
-    Information, PoFile,
+    Branch, Category, CategoryName, Domain, Information, Module, PoFile,
+    Release, Statistics,
 )
 
 
 class FixtureFactory(TestCase):
-    """ Fake Test case to create fixture.
-        To create a JSON fixture, run:
-        python manage.py test stats.tests.FixtureFactory.make_fixture
+    """
+    Fake Test case to create fixture.
+    To create a JSON fixture, run:
+    python manage.py test stats.tests.FixtureFactory.make_fixture
     """
 
     def make_fixture(self):
         # Creating models: Teams
-        t1 = Team.objects.create(name='fr', description="French",
+        t1 = Team.objects.create(
+            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/";)
+            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/";)
 
         # 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)
+        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')
 
         # Creating models: Persons/Roles
-        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')
+        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')
-        p2 = Person.objects.create(first_name='John', last_name='Coordinator',
-            email='coord example org', username='coord', svn_account='coord_fr')
+        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')
-        p3 = Person.objects.create(first_name='Alessio', last_name='Reviewer',
-            email='alessio example org', username='alessio')
+        p3 = Person.objects.create(
+            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')
         p0.delete()
 
         # Creating models: Modules
         gnome_hello = Module.objects.create(
-            name="gnome-hello", vcs_type="git",
+            name="gnome-hello",
+            vcs_type="git",
             vcs_root="https://gitlab.gnome.org/GNOME/gnome-hello.git";,
             vcs_web="https://gitlab.gnome.org/GNOME/gnome-hello/";,
             bugs_base="https://gitlab.gnome.org/GNOME/gnome-hello/issues";,
         )
         zenity = Module.objects.create(
-            name="zenity", vcs_type="git",
+            name="zenity",
+            vcs_type="git",
             vcs_root="https://gitlab.gnome.org/GNOME/zenity.git";,
             vcs_web="https://gitlab.gnome.org/GNOME/zenity/";,
             bugs_base="https://gitlab.gnome.org/GNOME/zenity/issues";,
         )
-        s_m_i = Module.objects.create(name="shared-mime-info", vcs_type="git",
+        s_m_i = Module.objects.create(
+            name="shared-mime-info",
+            vcs_type="git",
             description="Shared MIME Info",
             vcs_root="https://gitlab.freedesktop.org/xdg/shared-mime-info.git";,
             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>.")
+            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>.",
+        )
 
         # 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-doc' % mod.name] = Domain.objects.create(module=mod, name='help', description='User 
Guide', dtype='doc', layout='help/{lang}/{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'
+            )
 
         # Creating models: Branches
         Branch.checkout_on_creation = False
@@ -93,15 +114,15 @@ class FixtureFactory(TestCase):
         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)
-        rel2 = Release.objects.create(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)
+        rel1 = Release.objects.create(
+            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
+        )
+        rel3 = Release.objects.create(
+            name='freedesktop-org', status='xternal', description='freedesktop.org (non-GNOME)', 
string_frozen=False
+        )
 
         cat_name = CategoryName.objects.create(name='Desktop')
         Category.objects.create(release=rel1, branch=b1, name=cat_name)
@@ -112,28 +133,64 @@ class FixtureFactory(TestCase):
         # Creating models: Statistics
         # 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)
+        Statistics.objects.create(
+            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)
+        Statistics.objects.create(
+            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)
-        pofile = PoFile.objects.create(untranslated=20,
-            figures = [{"path": "figures/gnome-hello-new.png", "hash": "8a1fcc6f46a22a1f500cfef9ca51b481"}, 
{"path": "figures/gnome-hello-logo.png", "hash": "1ae47b7a7c4fbeb1f6bb72c0cf18d389"}])
-        Statistics.objects.create(branch=b1, domain=dom['gnome-hello-doc'], language=None, full_po=pofile, 
part_po=pofile)
-        pofile = PoFile.objects.create(translated=20,
-            figures = [{"translated": False, "path": "figures/gnome-hello-new.png", "hash": 
"8a1fcc6f46a22a1f500cfef9ca51b481", "fuzzy": True}, {"translated": False, "path": 
"figures/gnome-hello-logo.png", "hash": "1ae47b7a7c4fbeb1f6bb72c0cf18d389", "fuzzy": False}])
-        Statistics.objects.create(branch=b1, domain=dom['gnome-hello-doc'], language=l_fr, full_po=pofile, 
part_po=pofile)
+        Statistics.objects.create(
+            branch=b1, domain=dom['gnome-hello-ui'], language=l_it, full_po=pofile, part_po=pofile
+        )
+        pofile = PoFile.objects.create(
+            untranslated=20,
+            figures=[
+                {"path": "figures/gnome-hello-new.png", "hash": "8a1fcc6f46a22a1f500cfef9ca51b481"},
+                {"path": "figures/gnome-hello-logo.png", "hash": "1ae47b7a7c4fbeb1f6bb72c0cf18d389"},
+            ],
+        )
+        Statistics.objects.create(
+            branch=b1, domain=dom['gnome-hello-doc'], language=None, full_po=pofile, part_po=pofile
+        )
+        pofile = PoFile.objects.create(
+            translated=20,
+            figures=[
+                {
+                    "translated": False,
+                    "path": "figures/gnome-hello-new.png",
+                    "hash": "8a1fcc6f46a22a1f500cfef9ca51b481",
+                    "fuzzy": True,
+                },
+                {
+                    "translated": False,
+                    "path": "figures/gnome-hello-logo.png",
+                    "hash": "1ae47b7a7c4fbeb1f6bb72c0cf18d389",
+                    "fuzzy": False,
+                },
+            ],
+        )
+        Statistics.objects.create(
+            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)
+        Statistics.objects.create(
+            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)
+        Statistics.objects.create(
+            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)
         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)
+        Statistics.objects.create(
+            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)
         pofile = PoFile.objects.create(untranslated=259)
@@ -141,7 +198,9 @@ class FixtureFactory(TestCase):
         pofile = PoFile.objects.create(translated=259)
         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)
+        stat1 = Statistics.objects.create(
+            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)
         pofile = PoFile.objects.create(translated=259)
@@ -154,20 +213,37 @@ class FixtureFactory(TestCase):
         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)
+        Statistics.objects.create(
+            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)
+        Statistics.objects.create(
+            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)
+        Statistics.objects.create(
+            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',
-            description="Error regenerating POT file for zenity:\n<pre>intltool-update -g 'zenity' 
-p\nERROR: xgettext failed to generate PO template file.</pre>"))
+        stat1.information_set.add(
+            Information(
+                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>"
+                ),
+            )
+        )
 
         # Output fixture
         out_file = NamedTemporaryFile(suffix=".json", dir=".", delete=False)
-        call_command('dumpdata', *['auth.User', 'people', 'teams', 'languages', 'stats'],
-            indent=1, format='json', stdout=out_file)
+        call_command(
+            'dumpdata',
+            *['auth.User', 'people', 'teams', 'languages', 'stats'],
+            indent=1,
+            format='json',
+            stdout=out_file
+        )
         out_file.close()
         print("Fixture created in the file %s" % out_file.name)
diff --git a/stats/tests/tests.py b/stats/tests/tests.py
index 12d72f2f..f7a77c77 100644
--- a/stats/tests/tests.py
+++ b/stats/tests/tests.py
@@ -11,22 +11,23 @@ from django.conf import settings
 from django.contrib.auth.models import User
 from django.core import mail
 from django.core.exceptions import ValidationError
-from django.urls import reverse
 from django.test import TestCase
 from django.test.utils import override_settings
+from django.urls import reverse
 
 from common.utils import run_shell_command
+from languages.models import Language
+from people.models import Person
+from stats import utils
 from stats.models import (
     GLIB_PRESET, Module, Domain, Branch, Release, CategoryName, PoFile, Statistics,
     FakeLangStatistics, Information, UnableToCommit
 )
-from stats import utils
-from languages.models import Language
-
-from .utils import patch_shell_command, test_scratchdir
+from .utils import PatchShellCommand, test_scratchdir
 
 try:
     from translate.storage import subtitles  # NOQA
+
     has_translate_subtitle_support = True
 except ImportError:
     has_translate_subtitle_support = False
@@ -89,7 +90,8 @@ class ModuleTestCase(TestCase):
         )
         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'
+            '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]))
         self.assertContains(
@@ -106,7 +108,7 @@ class ModuleTestCase(TestCase):
     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')
-        with patch_shell_command() as cmds:
+        with PatchShellCommand() as cmds:
             br.checkout()
         self.assertTrue(
             'git clone https://gitlab.gnome.org/GNOME/eog.git %s/git/eog' % settings.SCRATCHDIR in cmds,
@@ -158,9 +160,11 @@ class ModuleTestCase(TestCase):
     @test_scratchdir
     def test_branch_stats(self):
         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)
+        ghost_stat = Statistics.objects.create(
+            branch=self.branch, domain=self.mod.domain_set.get(name='po'), language=lang
+        )
         # Check stats
-        with patch_shell_command(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')
         self.assertEqual(fr_po_stat.translated(), 44)
@@ -212,7 +216,7 @@ class ModuleTestCase(TestCase):
     def test_delete_branch(self):
         """ Deleting the master branch of a git module deletes the checkout dir """
         checkout_path = self.branch.co_path
-        branch = Branch.objects.create(name="gnome-hello-1-4", module = self.mod)
+        branch = Branch.objects.create(name="gnome-hello-1-4", module=self.mod)
         branch.delete()
         self.assertTrue(checkout_path.exists())
         self.branch.delete()
@@ -237,10 +241,12 @@ class ModuleTestCase(TestCase):
         coord.save()
         # Duplicate data
         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)}
+        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)
+            }
         )
         self.assertContains(response, "the form is not valid")
 
@@ -257,7 +263,7 @@ class ModuleTestCase(TestCase):
         b1.save(update_statistics=False)
         self.assertEqual(
             [b.name for b in sorted(self.mod.branch_set.all())],
-            ['master', 'a-branch', 'p-branch',]
+            ['master', 'a-branch', 'p-branch']
         )
         self.assertEqual(
             [b.name for b in self.mod.get_branches(reverse=True)],
@@ -279,7 +285,7 @@ class ModuleTestCase(TestCase):
     def test_string_frozen_mail(self):
         """ String change for a module of a string_frozen release should generate a message """
         mail.outbox = []
-        with patch_shell_command(only=['git ']):
+        with PatchShellCommand(only=['git ']):
             self.branch.update_stats(force=False)
 
         # Create a new file with translation
@@ -302,8 +308,8 @@ class ModuleTestCase(TestCase):
     def test_dynamic_po(self):
         """ Test the creation of a blank po file for a new language """
         tamil = Language.objects.create(name="Tamil", locale="ta")
-        with patch_shell_command(only=['git ']):
-            self.branch.update_stats(force=False) # At least POT stats needed
+        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
         )
@@ -363,7 +369,7 @@ class ModuleTestCase(TestCase):
         po_file = Path(__file__).parent / 'test.po'
         domain = self.mod.domain_set.get(name='po')
         fr_lang = Language.objects.get(locale='fr')
-        with patch_shell_command():
+        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>')
@@ -382,7 +388,7 @@ class ModuleTestCase(TestCase):
             'git commit -m Update French translation --author Author <someone example org>',
             'git push origin master', 'git log -n1 --format=oneline',
         )
-        with patch_shell_command() as cmds:
+        with PatchShellCommand() as cmds:
             branch.commit_po(po_file, domain, fr_lang, 'Author <someone example org>')
             for idx, cmd in enumerate(git_ops):
                 self.assertIn(cmd, cmds[idx])
@@ -394,7 +400,7 @@ class ModuleTestCase(TestCase):
             'git commit -m Add Bemba translation --author Author <someone example org>',
             'git push origin master'
         )
-        with patch_shell_command() as cmds:
+        with PatchShellCommand() as cmds:
             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):
@@ -411,7 +417,7 @@ class ModuleTestCase(TestCase):
             'git commit -m Update French translation --author Author <someone example org>',
             'git push origin master'
         )
-        with patch_shell_command() as cmds:
+        with PatchShellCommand() as cmds:
             branch.commit_po(po_file, domain, fr_lang, 'Author <someone example org>')
             for idx, cmd in enumerate(git_ops):
                 self.assertIn(cmd, cmds[idx])
@@ -447,7 +453,7 @@ class ModuleTestCase(TestCase):
         self.mod.vcs_root = self.mod.vcs_root.replace('git://', 'ssh://')
         self.mod.save()
 
-        with patch_shell_command(only=['git push', 'git fetch', 'git reset']) as cmds:
+        with PatchShellCommand(only=['git push', 'git fetch', 'git reset']) as cmds:
             commit_hash = branch.commit_po(po_file, domain, fr_lang, 'Author <someone example org>')
         update_repo_sequence = (
             'git checkout -f master', 'git fetch', 'git reset --hard origin/master',
@@ -457,7 +463,7 @@ class ModuleTestCase(TestCase):
             'git cherry-pick -x',
             'git push origin master',
         )
-        with patch_shell_command(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])
@@ -551,14 +557,20 @@ class DomainTests(TestModuleBase):
         )
         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'] + list(GLIB_PRESET) +
-             ['--keyword=Description',
-             '--msgid-bugs-address',
-             'https://gitlab.gnome.org/GNOME/testmodule/issues'],
-             {'GETTEXTDATADIRS': os.path.dirname(utils.ITS_DATA_DIR)}
+            (
+                [
+                    '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'
+                ],
+                {'GETTEXTDATADIRS': os.path.dirname(utils.ITS_DATA_DIR)}
             )
         )
 
@@ -670,7 +682,7 @@ class StatisticsTests(TestCase):
         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']['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
@@ -734,22 +746,26 @@ class StatisticsTests(TestCase):
             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': []
-                }.items():
+            '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')
+        stat = Statistics.objects.get(
+            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(branch=stat.branch, domain=stat.domain, 
language=stat.language, person=pers)
+        state = StateTranslating.objects.create(
+            branch=stat.branch, domain=stat.domain, language=stat.language, person=pers
+        )
         # Mark file completely translated
         self.assertNotEqual(stat.part_po, stat.full_po)
-        #stat.set_translation_stats('dummy', 136, 0, 0)
+        # stat.set_translation_stats('dummy', 136, 0, 0)
         self.assertEqual(stat.part_po, stat.full_po)
         # Check state is still translating
         state = State.objects.filter(branch=stat.branch, domain=stat.domain, language=stat.language)
@@ -790,6 +806,7 @@ class StatisticsTests(TestCase):
 
 class FigureTests(TestCase):
     fixtures = ['sample_data.json']
+
     def test_figure_view(self):
         url = reverse('docimages', args=['gnome-hello', 'help', 'master', 'fr'])
         response = self.client.get(url)
@@ -800,11 +817,16 @@ class FigureTests(TestCase):
         response = self.client.get(url)
         self.assertContains(response, "gnome-hello-new.png")
 
-    def test_figure_URLs(self):
+    def test_figure_urls(self):
         """ 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')
+        stat = Statistics.objects.get(
+            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')
+        self.assertEqual(
+            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])
 
     @test_scratchdir
@@ -952,7 +974,7 @@ class OtherTests(TestCase):
             name='webkit', vcs_type='svn', vcs_root='https://svn.webkit.org/repository'
         )
         repo = SVNRepo(Branch(name='HEAD', module=mod, vcs_subpath='trunk'))
-        with patch_shell_command() as cmds:
+        with PatchShellCommand() as cmds:
             repo.init_checkout()
         self.assertEqual(cmds, [
             'svn co --non-interactive https://svn.webkit.org/repository/webkit/trunk '
diff --git a/stats/tests/utils.py b/stats/tests/utils.py
index c4b8a88b..f028f5c5 100644
--- a/stats/tests/utils.py
+++ b/stats/tests/utils.py
@@ -9,7 +9,7 @@ from unittest.mock import patch
 from django.conf import settings
 
 
-class patch_shell_command:
+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).
@@ -47,8 +47,8 @@ def test_scratchdir(test_func):
     """ Decorator to temporarily use the scratchdir inside the test directory """
     @wraps(test_func)
     def decorator(self):
-        old_SCRATCHDIR = settings.SCRATCHDIR
-        old_POTDIR = settings.POTDIR
+        old_scratchdir = settings.SCRATCHDIR
+        old_potdir = settings.POTDIR
         settings.SCRATCHDIR = Path(tempfile.mkdtemp()) / 'scratch'
         settings.POTDIR = settings.SCRATCHDIR / "POT"
         settings.POTDIR.mkdir(parents=True)
@@ -59,6 +59,6 @@ def test_scratchdir(test_func):
             test_func(self)
         finally:
             shutil.rmtree(settings.SCRATCHDIR)
-            settings.SCRATCHDIR = old_SCRATCHDIR
-            settings.POTDIR = old_POTDIR
+            settings.SCRATCHDIR = old_scratchdir
+            settings.POTDIR = old_potdir
     return decorator
diff --git a/stats/utils.py b/stats/utils.py
index 9550daf0..c76fcd4d 100644
--- a/stats/utils.py
+++ b/stats/utils.py
@@ -1,5 +1,4 @@
 import hashlib
-import logging
 import os
 import re
 import shutil
@@ -8,14 +7,15 @@ from itertools import islice
 from pathlib import Path
 from unittest.mock import MagicMock
 
-from translate.tools import pogrep, pocount
-
 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 translate.storage.base import TranslationStore
+from translate.tools import pogrep, pocount
+
 from common.utils import run_shell_command, send_mail
 from . import potdiff
 
@@ -24,17 +24,17 @@ C_ENV = {"LC_ALL": "C", "LANG": "C", "LANGUAGE": "C"}
 
 NOT_CHANGED = 0
 CHANGED_ONLY_FORMATTING = 1
-CHANGED_WITH_ADDITIONS  = 2
-CHANGED_NO_ADDITIONS    = 3
+CHANGED_WITH_ADDITIONS = 2
+CHANGED_NO_ADDITIONS = 3
 
 ITSTOOL_PATH = getattr(settings, 'ITSTOOL_PATH', '')
 ITS_DATA_DIR = settings.SCRATCHDIR / 'data' / 'its'
 
 # monkey-patch ttk (https://github.com/translate/translate/issues/2129)
-from translate.storage.base import TranslationStore
 orig_addunit = TranslationStore.addunit
 
-def patchedAddunit(self, unit):
+
+def patched_add_unit(self, unit):
     # Prevent two header units in the same store
     if unit.isheader() and len(self.units) and self.units[0].isheader():
         unit._store = self
@@ -42,7 +42,8 @@ def patchedAddunit(self, unit):
     else:
         orig_addunit(self, unit)
 
-TranslationStore.addunit = patchedAddunit
+
+TranslationStore.addunit = patched_add_unit
 
 
 class UndetectableDocFormat(Exception):
@@ -233,7 +234,7 @@ class MakefileWrapper:
 
 class MesonfileWrapper(MakefileWrapper):
     i18n_gettext_kwargs = {'po_dir', 'data_dirs', 'type', 'languages', 'args', 'preset'}
-    gnome_yelp_kwargs= {'sources', 'media', 'symlink_media', 'languages'}
+    gnome_yelp_kwargs = {'sources', 'media', 'symlink_media', 'languages'}
     ignorable_kwargs = {'install_dir'}
     readable = True
 
@@ -249,9 +250,15 @@ class MesonfileWrapper(MakefileWrapper):
         )
         # 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 = content.replace(
+            'true', 'True'
+        ).replace(
+            'false', 'False'
+        ).replace(
+            "i18n = import('i18n')", ''
+        ).replace(
+            "gnome = import('gnome')", ''
+        )
         return content
 
     @cached_property
@@ -356,14 +363,17 @@ def sort_object_list(lst, sort_meth):
     templist.sort()
     return [obj_ for (key1, obj_) in templist]
 
+
 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)
 
+
 def stripHTML(string):
     replacements = {"<ul>": "\n", "</ul>": "\n", "<li>": " * ", "\n</li>": "", "</li>": ""}
     return multiple_replace(replacements, string)
 
+
 def ellipsize(val, length):
     if len(val) > length:
         val = "%s..." % val[:length]
@@ -375,6 +385,7 @@ def check_program_presence(prog_name):
     status, output, err = run_shell_command(['which', prog_name])
     return status == 0
 
+
 def po_grep(in_file, out_file, filter_):
     if filter_ == "-":
         return
@@ -394,11 +405,14 @@ def po_grep(in_file, out_file, filter_):
             pass
     # command-line variant:
     """
-    cmd = "pogrep --invert-match --header --search=locations --regexp \"gschema\\.xml\\.in|schemas\\.in\" 
%(full_po)s %(part_po)s" % {
+    cmd = 'pogrep --invert-match --header --search=locations --regexp ' \
+          '"gschema\\.xml\\.in|schemas\\.in\" %(full_po)s %(part_po)s' % {
         'full_po': in_file,
         'part_po': out_file,
     }
-    run_shell_command(cmd)"""
+    run_shell_command(cmd)
+    """
+
 
 def check_potfiles(po_path):
     """Check if there were any problems regenerating a POT file (intltool-update -m).
@@ -409,25 +423,30 @@ def check_potfiles(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.")) )
+        errors.append(("error", gettext_noop("Errors while running “intltool-update -m” check.")))
 
     missing = po_path / "missing"
     if missing.exists():
         with missing.open("r") as fh:
             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>")
-                )
+                (
+                    "warn",
+                    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"
     if notexist.exists():
         with notexist.open("r") as fh:
             errors.append(
-                ("error",
-                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>")
+                (
+                    "error",
+                    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>")
                 )
             )
     return errors
@@ -512,7 +531,7 @@ def check_po_conformity(pofile):
     if status != STATUS_OK:
         errors.append((
             "warn",
-             gettext_noop("PO file “%s” is not UTF-8 encoded.") % pofile.name
+            gettext_noop("PO file “%s” is not UTF-8 encoded.") % pofile.name
         ))
     return errors
 
@@ -538,13 +557,13 @@ def check_po_quality(pofile, filters):
 def po_file_stats(pofile):
     """Compute pofile translation statistics."""
     res = {
-        'translated' : 0,
-        'fuzzy' : 0,
-        'untranslated' : 0,
+        'translated': 0,
+        'fuzzy': 0,
+        'untranslated': 0,
         'translated_words': 0,
         'fuzzy_words': 0,
         'untranslated_words': 0,
-        'errors' : [],
+        'errors': [],
     }
 
     if not pofile.exists():
@@ -585,6 +604,7 @@ def read_linguas_file(full_path):
         '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:
@@ -602,13 +622,13 @@ def insert_locale_in_linguas(linguas_path, locale):
 def get_ui_linguas(branch, subdir):
     """Get language list in one of po/LINGUAS, configure.ac or configure.in"""
 
-    LINGUAShere = branch.co_path / subdir/ "LINGUAS"
-    LINGUASpo = branch.co_path / "po" / "LINGUAS"  # if we're in eg. po-locations/
+    linguas_here = branch.co_path / subdir / "LINGUAS"
+    lingua_po = branch.co_path / "po" / "LINGUAS"  # if we're in eg. po-locations/
 
     # is "lang" listed in either of po/LINGUAS, ./configure.ac(ALL_LINGUAS) or ./configure.in(ALL_LINGUAS)
-    for LINGUAS in [LINGUAShere, LINGUASpo]:
-        if LINGUAS.exists():
-            return read_linguas_file(LINGUAS)
+    for linguas in [linguas_here, lingua_po]:
+        if linguas.exists():
+            return read_linguas_file(linguas)
     # 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:
@@ -621,9 +641,9 @@ def get_ui_linguas(branch, subdir):
         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.") }
+                    '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):
@@ -632,18 +652,19 @@ def get_doc_linguas(branch, subdir):
     po_path = branch.co_path / subdir
 
     # Prioritize LINGUAS files when it exists.
-    LINGUAS_path = po_path / "LINGUAS"
-    if LINGUAS_path.exists():
-        return read_linguas_file(LINGUAS_path)
+    linguas_path = po_path / "LINGUAS"
+    if linguas_path.exists():
+        return read_linguas_file(linguas_path)
 
     linguas_file = MakefileWrapper.find_file(branch, [po_path, branch.co_path])
     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': 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.") }
+            'error': gettext_noop("DOC_LINGUAS list doesn’t include this language.")}
 
 
 def collect_its_data():
@@ -683,21 +704,22 @@ def get_fig_stats(pofile, doc_format):
         return []
     lines = output.split('\n')
     while lines[0][0] != "#":
-        lines = lines[1:] # skip warning messages at the top of the output
+        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):
+    for i, line in islice(enumerate(lines), 0, None, 3 + before_lines):
         # TODO: add image size
         fig = {"path": '', "hash": ''}
-        m = doc_format.img_regex.match(lines[i+before_lines])
+        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["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
 
+
 def check_identical_figures(fig_stats, base_path, lang):
     errors = []
     for fig in fig_stats:
@@ -705,7 +727,9 @@ def check_identical_figures(fig_stats, base_path, lang):
         if trans_path.exists():
             trans_hash = compute_md5(trans_path)
             if fig['hash'] == trans_hash:
-                errors.append(("warn-ext", "Figures should not be copied when identical to original (%s)." % 
trans_path))
+                errors.append(
+                    ("warn-ext", "Figures should not be copied when identical to original (%s)." % 
trans_path)
+                )
     return errors
 
 
@@ -739,7 +763,7 @@ def exclude_untrans_messages(potfile):
         fh.seek(0)
         skip_unit = False
         for line in lines:
-            if not exclude_message in line.lower() and not skip_unit:
+            if exclude_message not in line.lower() and not skip_unit:
                 fh.write(line)
             else:
                 # A blank line is resetting skip_unit
@@ -754,7 +778,7 @@ def is_po_reduced(file_path):
 
 def compute_md5(full_path):
     m = hashlib.md5()
-    block_size=2**13
+    block_size = 2 ** 13
     with full_path.open('rb') as fh:
         while True:
             data = fh.read(block_size)
@@ -789,6 +813,7 @@ def url_join(base, *args):
         url += arg
     return url
 
+
 class Profiler:
     def __init__(self):
         self.start = time.clock()
diff --git a/stats/views.py b/stats/views.py
index 67173f86..1954226f 100644
--- a/stats/views.py
+++ b/stats/views.py
@@ -24,7 +24,7 @@ def modules(request, format='html'):
         data = serializers.serialize(format, all_modules)
         return HttpResponse(data, content_type=MIME_TYPES[format])
     context = {
-        'pageSection':  "module",
+        'pageSection': "module",
         'modules': sort_object_list(all_modules, 'get_description'),
     }
     return render(request, 'module_list.html', context)
@@ -42,10 +42,10 @@ def module(request, module_name):
             branch.get_doc_stats(mandatory_langs=langs)
 
     context = {
-        'pageSection':  "module",
+        'pageSection': "module",
         'module': mod,
         'branches': branches,
-        'non_standard_repo_msg' : _(settings.VCS_HOME_WARNING),
+        '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)
@@ -102,7 +102,7 @@ def module_edit_branches(request, module_name):
                     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)
@@ -163,27 +163,32 @@ def branch_refresh(request, branch_id):
 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)
+        stat = Statistics.objects.get(
+            branch__module=mod.id,
+            branch__name=branch_name,
+            domain__name=potbase,
+            language__locale=langcode
+        )
     except Statistics.DoesNotExist:
-        pot_stat = get_object_or_404(Statistics,
+        pot_stat = get_object_or_404(
+            Statistics,
             branch__module=mod.id,
             branch__name=branch_name,
             domain__name=potbase,
-            language__isnull=True)
+            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,
+        'module': mod,
+        'stat': stat,
+        'locale': stat.language.locale,
         'figstats': stat.fig_stats(),
     }
     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 """
     try:
@@ -217,9 +222,9 @@ def dynamic_po(request, module_name, branch_name, domain_name, filename):
     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,
+            'name': person.name,
             'email': person.email,
-            'year' : date.today().year,
+            'year': date.today().year,
         }
     else:
         dyn_content += "# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n#\n"
@@ -248,32 +253,35 @@ def dynamic_po(request, module_name, branch_name, domain_name, filename):
         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]))
     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')
+    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",
+        'pageSection': "releases",
         'active_releases': active_releases,
-        'old_releases'   : old_releases,
+        'old_releases': old_releases,
     }
     return render(request, 'release_list.html', context)
 
+
 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])
+        return render(
+            request, 'release_detail.xml', {'release': release}, content_type=MIME_TYPES[format]
+        )
     context = {
         'pageSection': "releases",
         'user_language': get_user_locale(request),
@@ -281,6 +289,7 @@ def release(request, release_name, format='html'):
     }
     return render(request, 'release_detail.html', context)
 
+
 def compare_by_releases(request, dtype, rels_to_compare):
     releases = []
     try:
diff --git a/vertimus/tests/tests.py b/vertimus/tests/tests.py
index 82bc7cd9..362df2cb 100644
--- a/vertimus/tests/tests.py
+++ b/vertimus/tests/tests.py
@@ -22,7 +22,7 @@ from teams.models import Role, Team
 from teams.tests import TeamsAndRolesMixin
 from stats.models import Module, Branch, Release, Category, CategoryName, Domain, Statistics
 from stats.tests.tests import TestModuleBase
-from stats.tests.utils import patch_shell_command, test_scratchdir
+from stats.tests.utils import PatchShellCommand, test_scratchdir
 from vertimus.models import (
     Action, ActionArchived, ActionCI, ActionUNDO, ActionWC, State, StateCommitted,
     StateCommitting, StateNone, StateProofread, StateProofreading, StateToCommit,
@@ -466,7 +466,7 @@ class VertimusTest(TeamsAndRolesMixin, TestCase):
         self.assertTrue(form.is_valid())
         # path needed when copying file to commit
         (self.b.co_path / 'po').mkdir(parents=True, exist_ok=True)
-        with patch_shell_command(only=['git ']) as cmds:
+        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(


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