[damned-lies] refactor: fix few linter issues in stats



commit d53265a6d54ba1703c28eb1faaa57248489d92d0
Author: Guillaume Bernard <associations guillaume-bernard fr>
Date:   Sun Apr 18 11:29:54 2021 +0200

    refactor: fix few linter issues in stats

 stats/admin.py       |   9 +-
 stats/doap.py        |  23 ++--
 stats/forms.py       |  24 ++--
 stats/models.py      | 364 +++++++++++++++++++++++++++++----------------------
 stats/tests/tests.py |   8 ++
 5 files changed, 248 insertions(+), 180 deletions(-)
---
diff --git a/stats/admin.py b/stats/admin.py
index 1bcd679b..0f334446 100644
--- a/stats/admin.py
+++ b/stats/admin.py
@@ -174,9 +174,8 @@ class ReleaseAdmin(admin.ModelAdmin):
                     branch = cat.branch
                 if branch in branch_seen:
                     continue
-                else:
-                    Category.objects.create(release=new_rel, branch=branch, name=cat.name)
-                    branch_seen.add(branch)
+                Category.objects.create(release=new_rel, branch=branch, name=cat.name)
+                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)"
@@ -213,18 +212,22 @@ class ReleaseAdmin(admin.ModelAdmin):
         return render(request, 'admin/delete_release_confirmation.html', context)
     delete_release.short_description = "Delete release (and associated branches)"
 
+
 class InformationInline(admin.TabularInline):
     model = Information
     extra = 0
 
+
 class StatisticsAdmin(admin.ModelAdmin):
     search_fields = ('language__name', 'branch__module__name')
     raw_id_fields = ('branch', 'domain', 'language', 'full_po', 'part_po')
     inlines = [ InformationInline ]
 
+
 class PoFileAdmin(admin.ModelAdmin):
     search_fields = ('path',)
 
+
 admin.site.register(Statistics, StatisticsAdmin)
 admin.site.register(PoFile, PoFileAdmin)
 admin.site.register(Branch, BranchAdmin)
diff --git a/stats/doap.py b/stats/doap.py
index 182aab40..af20ca68 100644
--- a/stats/doap.py
+++ b/stats/doap.py
@@ -45,6 +45,7 @@ class DoapParser:
             return homepage_tag.attrib.get('{http://www.w3.org/1999/02/22-rdf-syntax-ns#}resource')
         return None
 
+
 def update_doap_infos(module):
     """ Should only be called inside an "update-stats" context of a master branch,
         so there is no need for any extra checkout/locking strategy """
@@ -64,24 +65,26 @@ def update_doap_infos(module):
     current_maintainers = dict([(m.email or m.username, m) for m in module.maintainers.all()])
 
     # Using email or username as unique identifier
-    for maint in doap_maintainers:
-        maint_key = maint['email'] or maint['account'] or slugify(maint['name'])
-        if maint_key in current_maintainers.keys():
-            del current_maintainers[maint_key]
+    for maintainer in doap_maintainers:
+        maintainer_key = maintainer['email'] or maintainer['account'] or slugify(maintainer['name'])
+        if maintainer_key in current_maintainers.keys():
+            del current_maintainers[maintainer_key]
         else:
             # Add new maintainer
-            pers = Person.get_by_attr('email', maint['email'])
+            pers = Person.get_by_attr('email', maintainer['email'])
             if not pers:
-                pers = Person.get_by_attr('username', maint['account'] or slugify(maint['name']))
+                pers = Person.get_by_attr('username', maintainer['account'] or slugify(maintainer['name']))
             if not pers:
-                pers = Person(username=maint['account'] or slugify(maint['name']), email=maint['email'] or 
'',
-                              password='!', svn_account=maint['account'], last_name=maint['name'])
+                pers = Person(
+                    username=maintainer['account'] or slugify(maintainer['name']), email=maintainer['email'] 
or '',
+                    password='!', svn_account=maintainer['account'], last_name=maintainer['name']
+                )
                 pers.save()
             module.maintainers.add(pers)
 
-    for key, maint in current_maintainers.items():
+    for maintainer in current_maintainers.values():
         # Drop maintainers not in doap file
-        module.maintainers.remove(maint)
+        module.maintainers.remove(maintainer)
 
     # *********** Update homepage
     home = tree.parse_homepage()
diff --git a/stats/forms.py b/stats/forms.py
index 23788e01..fbdac4e0 100644
--- a/stats/forms.py
+++ b/stats/forms.py
@@ -20,19 +20,19 @@ class ModuleBranchForm(forms.Form):
         self.branch_fields = []
         default_cat_name = None
         for branch in module.get_branches(reverse=True):
-            categs = branch.category_set.order_by('name', 'release__name')
-            if len(categs):
-                for cat in categs:
-                    self.fields[str(cat.id)] = ReleaseField(queryset=Release.objects.all(),
-                                                            label=branch.name,
-                                                            initial=cat.release.pk)
-                    self.fields[str(cat.id)+'_cat'] = forms.ModelChoiceField(
+            categories = list(branch.category_set.order_by('name', 'release__name'))
+            if len(categories) > 0:
+                for category in categories:
+                    self.fields[str(category.id)] = ReleaseField(
+                        queryset=Release.objects.all(), label=branch.name, initial=category.release.pk
+                    )
+                    self.fields[str(category.id)+'_cat'] = forms.ModelChoiceField(
                         queryset=CategoryName.objects.all(),
                         required=False,
-                        initial=cat.name
+                        initial=category.name
                     )
-                    self.branch_fields.append((str(cat.id), str(cat.id)+'_cat'))
-                default_cat_name = cat.name
+                    self.branch_fields.append((str(category.id), f'{category.id}_cat'))
+                default_cat_name = categories[-1].name
             else:
                 # Branch is not linked to any release
                 self.fields[branch.name] = ReleaseField(queryset=Release.objects.all(),
@@ -42,7 +42,7 @@ class ModuleBranchForm(forms.Form):
                     required=False,
                     initial=default_cat_name
                 )
-                self.branch_fields.append((branch.name, branch.name+'_cat'))
+                self.branch_fields.append((branch.name, branch.name + '_cat'))
 
         self.fields['new_branch'] = forms.CharField(required=False)
         self.fields['new_branch_release'] = ReleaseField(queryset=Release.objects.all())
@@ -53,7 +53,7 @@ class ModuleBranchForm(forms.Form):
 
     def get_branches(self):
         for rel_field, cat_field in self.branch_fields:
-            yield (self[rel_field], self[cat_field])
+            yield self[rel_field], self[cat_field]
 
     def clean(self):
         cleaned_data = super().clean()
diff --git a/stats/models.py b/stats/models.py
index eea291d9..8505b5de 100644
--- a/stats/models.py
+++ b/stats/models.py
@@ -1,5 +1,7 @@
 import logging
-import os, sys, re
+import os
+import sys
+import re
 import shutil
 import threading
 from collections import Counter, OrderedDict
@@ -121,7 +123,7 @@ class Module(models.Model):
         return self.name
 
     def get_comment(self):
-        comment = self.comment and _(self.comment) or ""
+        comment = _(self.comment) if self.comment else ""
         if self.ext_platform:
             if comment:
                 comment += "<br/>"
@@ -139,18 +141,16 @@ class Module(models.Model):
 
     def get_bugs_i18n_url(self, content=None):
         if 'gitlab' in self.bugs_base:
-            return utils.url_join(self.bugs_base, '?state=opened&label_name[]=8.%20Translation')
+            link = utils.url_join(self.bugs_base, '?state=opened&label_name[]=8.%20Translation')
             if content:
-                link += "&search=%s" % content
+                link += "&search=%s" % content.replace(' ', '+')
             return link
-        else:
-            return None
+        return None
 
     def get_bugs_enter_url(self):
         if 'gitlab' in self.bugs_base:
             return utils.url_join(self.bugs_base, 'new')
-        else:
-            return self.bugs_base
+        return self.bugs_base
 
     def get_branches(self, reverse=False):
         """ Return module branches, in ascending order by default (descending order if reverse == True) """
@@ -158,12 +158,11 @@ class Module(models.Model):
 
     def get_head_branch(self):
         """ Returns the HEAD (trunk, master, ...) branch of the module """
-        branch = self.branch_set.filter(name__in = BRANCH_HEAD_NAMES)
+        branch = self.branch_set.filter(name__in=BRANCH_HEAD_NAMES)
         if branch:
             # First one (if many something is wrong :-/)
             return branch[0]
-        else:
-            return None
+        return None
 
     def can_edit_branches(self, user):
         """ Returns True for superusers, users with adequate permissions or maintainers of the module """
@@ -187,7 +186,6 @@ class ModuleLock:
         We use filesystem directories creation/deletion to act as global lock mecanism
     """
     def __init__(self, mod):
-        assert isinstance(mod, Module)
         self.module = mod
         self.dirpath = settings.LOCK_DIR / f"updating-{self.module.name}"
 
@@ -287,7 +285,7 @@ class Branch(models.Model):
     def __lt__(self, other):
         if self.name in BRANCH_HEAD_NAMES:
             return True
-        elif other.name in BRANCH_HEAD_NAMES:
+        if other.name in BRANCH_HEAD_NAMES:
             return False
         # Splitting so gnome-3-2 < gnome-3-10
         return ((-self.weight,) + split_name(self.name)) > ((-other.weight,) + split_name(other.name))
@@ -298,7 +296,7 @@ class Branch(models.Model):
 
     @property
     def img_url_prefix(self):
-        return self.module.vcs_type == 'git' and "raw" or ""
+        return 'raw' if self.module.vcs_type == 'git' else ''
 
     def is_head(self):
         return self.name in BRANCH_HEAD_NAMES
@@ -318,12 +316,12 @@ class Branch(models.Model):
         new_hash = utils.compute_md5(full_path)
         if self.file_hashes and self.file_hashes.get(rel_path, None) == new_hash:
             return False
-        else:
-            if not self.file_hashes:
-                self.file_hashes = {}
-            self.file_hashes[rel_path] = new_hash
-            self.save(update_statistics=False)
-            return True
+
+        if not self.file_hashes:
+            self.file_hashes = {}
+        self.file_hashes[rel_path] = new_hash
+        self.save(update_statistics=False)
+        return True
 
     def has_string_frozen(self):
         """ Returns true if the branch is contained in at least one string frozen release """
@@ -339,20 +337,18 @@ class Branch(models.Model):
     def get_vcs_web_url(self):
         if self.module.vcs_type in ('hg', 'git'):
             return self.module.vcs_web
-        elif self.vcs_subpath:
+        if self.vcs_subpath:
             return utils.url_join(self.module.vcs_web, self.vcs_subpath)
-        elif self.is_head():
+        if self.is_head():
             return utils.url_join(self.module.vcs_web, "trunk")
-        else:
-            return utils.url_join(self.module.vcs_web, "branches", self.name)
+        return utils.url_join(self.module.vcs_web, "branches", self.name)
 
     def get_vcs_web_log_url(self):
         """ Link to browsable commit log """
         if self.module.vcs_type == 'git':
             return utils.url_join(self.module.vcs_web, 'commits', self.name)
-        else:
-            # Not implemented for other VCS
-            return ""
+        # Not implemented for other VCS
+        return ""
 
     @cached_property
     def co_path(self):
@@ -390,11 +386,11 @@ class Branch(models.Model):
         dirname.mkdir(parents=True, exist_ok=True)
         return dirname
 
-    def get_stats(self, typ, mandatory_langs=[]):
+    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 a list of language objects whose stats should be added even if no translation 
exists.
+            mandatory_langs is an iterable of language objects whose stats should be added even if no 
translation exists.
         """
         stats = OrderedDict()
         stats_langs = {}
@@ -416,17 +412,17 @@ class Branch(models.Model):
                 if lang not in stats_langs[domain] and stats[domain][0].full_po:
                     stats[domain].append(FakeLangStatistics(stats[domain][0], lang))
         # Sort
-        for key, doms in stats.items():
-            #Sort stats, pot first, then translated (desc), then language name
+        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()))
         return stats
 
-    def get_doc_stats(self, mandatory_langs=[]):
+    def get_doc_stats(self, mandatory_langs=()):
         if not self._doc_stats:
             self._doc_stats = self.get_stats('doc', mandatory_langs)
         return self._doc_stats
 
-    def get_ui_stats(self, mandatory_langs=[]):
+    def get_ui_stats(self, mandatory_langs=()):
         if not self._ui_stats:
             self._ui_stats = self.get_stats('ui', mandatory_langs)
         return self._ui_stats
@@ -476,8 +472,7 @@ class Branch(models.Model):
                 # *****************************
                 previous_pot = self.output_dir(dom.dtype) / ('%s.%s.pot' % (dom.potbase(), 
self.name_escaped))
                 if not potfile:
-                    logger.error("Can't generate POT file for %s/%s." % (
-                        self.module.name, dom.name))
+                    logger.error("Can’t generate POT file for %s/%s.", self.module.name, dom.name)
                     if previous_pot.exists():
                         # Use old POT file
                         potfile = previous_pot
@@ -503,7 +498,7 @@ class Branch(models.Model):
                 if potfile != previous_pot:
                     try:
                         shutil.copyfile(str(potfile), str(previous_pot))
-                    except Exception:
+                    except Exception:  # FIXME: too broad exception
                         pot_stat.set_error('error', gettext_noop("Can’t copy new POT file to public 
location."))
 
                 # Send pot_has_changed signal
@@ -512,7 +507,9 @@ class Branch(models.Model):
 
                 # 7. Update language po files and update DB
                 # *****************************************
-                stats_with_ext_errors = Statistics.objects.filter(branch=self, domain=dom, 
information__type__endswith='-ext')
+                stats_with_ext_errors = Statistics.objects.filter(
+                    branch=self, domain=dom, information__type__endswith='-ext'
+                )
                 langs_with_ext_errors = [stat.language.locale for stat in stats_with_ext_errors]
                 dom_langs = dom.get_lang_files(self.co_path)
                 for lang, pofile in dom_langs:
@@ -522,7 +519,7 @@ class Branch(models.Model):
                     if (not force and changed_status in (utils.NOT_CHANGED, utils.CHANGED_ONLY_FORMATTING)
                             and outpo.exists()
                             and pofile.stat().st_mtime < outpo.stat().st_mtime
-                            and not lang in langs_with_ext_errors):
+                            and lang not in langs_with_ext_errors):
                         continue
 
                     # msgmerge po with recent pot
@@ -555,16 +552,18 @@ class Branch(models.Model):
                         stat.set_error('warn-ext', linguas['error'])
 
                 # Delete stats for unexisting langs
-                Statistics.objects.filter(branch=self, domain=dom
-                    ).exclude(models.Q(language__isnull=True) | models.Q(language__locale__in=[dl[0] for dl 
in dom_langs])
-                    ).delete()
+                Statistics.objects.filter(
+                    branch=self, domain=dom
+                ).exclude(
+                    models.Q(language__isnull=True) | models.Q(language__locale__in=[dl[0] for dl in 
dom_langs])
+                ).delete()
             # Check if doap file changed
             if self.is_head() and self.file_changed("%s.doap" % self.module.name):
                 update_doap_infos(self.module)
 
     def checkout(self):
         """ Do a checkout or an update of the VCS files """
-        logger.debug("Checking '%s.%s' out to '%s'…" % (self.module.name, self.name, self.co_path))
+        logger.debug("Checking “%s.%s” out to “%s”…", self.module.name, self.name, self.co_path)
         self._repo.checkout()
 
     def update_repo(self):
@@ -573,7 +572,7 @@ class Branch(models.Model):
         WARNING: the calling method should acquire a lock
         for the module to not mix checkouts in different threads/processes.
         """
-        logger.debug("Updating '%s.%s' (in '%s')..." % (self.module.name, self.name, self.co_path))
+        logger.debug("Updating “%s.%s” (in “%s”)…", self.module.name, self.name, self.co_path)
         self._repo.update()
 
     def commit_po(self, po_file, domain, language, author):
@@ -625,6 +624,7 @@ DOMAIN_TYPE_CHOICES = (
     ('doc', 'Documentation')
 )
 
+
 class Domain(models.Model):
     POT_METHOD_CHOICES = (
         ('auto', 'auto detected'),
@@ -647,13 +647,21 @@ class Domain(models.Model):
     pot_params = models.CharField(
         max_length=100, blank=True, help_text="Only for shell or URL methods"
     )
-    extra_its_dirs = models.TextField(blank=True,
-        help_text="colon-separated directories containing extra .its/.loc files for gettext")
-    linguas_location = models.CharField(max_length=50, blank=True,
-        help_text="""Use 'no' for no LINGUAS check, or path/to/file#variable for a non-standard location.
-            Leave blank for standard location (ALL_LINGUAS in LINGUAS/configure.ac/.in for UI and 
DOC_LINGUAS in Makefile.am for DOC)""")
-    red_filter = models.TextField(blank=True,
-        help_text="""pogrep filter to strip po file from unprioritized strings (format: location|string, "-" 
for no filter)""")
+    extra_its_dirs = models.TextField(
+        blank=True,
+        help_text="colon-separated directories containing extra .its/.loc files for gettext"
+    )
+    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)"
+    )
+    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)"
+    )
     # Allow to specify the branches to which this domain applies
     branch_from = models.ForeignKey(Branch, null=True, blank=True, on_delete=models.PROTECT, 
related_name='+')
     branch_to = models.ForeignKey(Branch, null=True, blank=True, on_delete=models.PROTECT, related_name='+')
@@ -677,16 +685,14 @@ class Domain(models.Model):
         if self.name[:2] == 'po':
             # e.g. replace po by gimp (for ui), or po-plugins by gimp-plugins
             return self.module.name + self.name[2:]
-        elif self.name == 'help':
+        if self.name == 'help':
             return "%s-help" % self.module.name
-        else:
-            return self.name
+        return self.name
 
     def get_description(self):
         if self.description:
             return _(self.description)
-        else:
-            return self.potbase()
+        return self.potbase()
 
     def has_standard_location(self):
         return (self.dtype == 'ui' and self.layout == 'po/{lang}.po') or (
@@ -717,6 +723,7 @@ class Domain(models.Model):
         [(lang, lang_file), ...] -> lang_file as Path.
         """
         flist = []
+
         def extract_lang(path):
             path_str = str(path)
             start = len(str(base_path)) + self.layout.find('{lang}') + 1
@@ -819,10 +826,9 @@ class Domain(models.Model):
 
         if errors:
             return "", errors
-        elif not potfile.exists():
+        if not potfile.exists():
             return "", (("error", gettext_noop("Unable to generate POT file")),)
-        else:
-            return potfile, ()
+        return potfile, ()
 
     def get_xgettext_command(self, branch):
         pot_command = ['xgettext',
@@ -906,22 +912,29 @@ class Domain(models.Model):
                     file_path = self.linguas_location
                 linguas_file = utils.MakefileWrapper(branch, base_path / file_path)
                 langs = linguas_file.read_variable(variable)
-                return {'langs': langs.split() if langs is not None else None,
-                        'error': gettext_noop("Entry for this language is not present in %(var)s variable in 
%(file)s file." % {
-                            'var': variable, 'file': file_path})}
+                return {
+                    'langs': langs.split() if langs is not None else None,
+                    'error': gettext_noop(
+                        "Entry for this language is not present in %(var)s variable in %(file)s file." % {
+                            'var': variable, 'file': file_path
+                        }
+                    )
+                }
         # Standard linguas location
         if self.dtype == 'ui':
             return utils.get_ui_linguas(branch, self.base_dir)
-        elif self.dtype == 'doc':
+        if self.dtype == 'doc':
             return utils.get_doc_linguas(branch, self.base_dir)
-        else:
-            raise ValueError("Domain dtype should be one of 'ui', 'doc'")
+        raise ValueError("Domain dtype should be one of 'ui', 'doc'")
+
 
 RELEASE_STATUS_CHOICES = (
     ('official', 'Official'),
     ('unofficial', 'Unofficial'),
     ('xternal', 'External')
 )
+
+
 class Release(models.Model):
     name = models.SlugField(max_length=20)
     description = models.CharField(max_length=50)
@@ -945,12 +958,14 @@ class Release(models.Model):
     def excluded_domains(self):
         # Compute domains which doesn't apply for this release due to limited domain
         # (by branch_from/branch_to).
-        limited_stats = Statistics.objects.select_related('branch', 'domain'
-            ).filter(branch__releases=self
-            ).filter(language__isnull=True
-            ).filter(models.Q(domain__branch_from__isnull=False) |
-                     models.Q(domain__branch_to__isnull=False))
-        return set([st.domain for st in limited_stats if not st.branch.has_domain(st.domain)])
+        limited_stats = Statistics.objects.select_related(
+            'branch', 'domain'
+        ).filter(
+            branch__releases=self
+        ).filter(
+            language__isnull=True
+        ).filter(models.Q(domain__branch_from__isnull=False) | models.Q(domain__branch_to__isnull=False))
+        return {st.domain for st in limited_stats if not st.branch.has_domain(st.domain)}
 
     @classmethod
     def total_by_releases(cls, dtype, releases):
@@ -987,19 +1002,24 @@ class Release(models.Model):
                     stats[locale]['stats'][releases.index(rel)] = line['trans']
 
         # Compute percentages
-        def perc(x, y):
+        def compute_percentage(x, y):
             return int(x / y * 100)
-        for k in stats.keys():
-            stats[k]['stats'] = list(map(perc, stats[k]['stats'], totals))
+
+        for k in stats:
+            stats[k]['stats'] = list(map(compute_percentage, stats[k]['stats'], totals))
             stats[k]['diff'] = stats[k]['stats'][-1] - stats[k]['stats'][0]
         return stats
 
     def total_strings(self):
         """ Returns the total number of strings in the release as a tuple (doc_total, ui_total) """
         # Use pot stats to compute total sum
-        qs = Statistics.objects.filter(branch__category__release=self, language__isnull=True
-            ).exclude(domain__in=self.excluded_domains).values('domain__dtype'
-            ).annotate(untrans=models.Sum('full_po__untranslated'))
+        qs = Statistics.objects.filter(
+            branch__category__release=self, language__isnull=True
+        ).exclude(
+            domain__in=self.excluded_domains
+        ).values(
+            'domain__dtype'
+        ).annotate(untrans=models.Sum('full_po__untranslated'))
         totals = Counter()
         for line in qs:
             totals[line['domain__dtype']] += line['untrans']
@@ -1008,13 +1028,19 @@ class Release(models.Model):
     def total_part_for_all_langs(self):
         """ Return total partial UI strings for each language """
         total_part_ui_strings = {}
-        all_ui_pots = Statistics.objects.select_related('part_po'
-            ).exclude(domain__in=self.excluded_domains
-            ).filter(language__isnull=True, branch__releases=self, domain__dtype='ui')
-        all_ui_stats = Statistics.objects.select_related('part_po', 'language'
-            ).exclude(domain__in=self.excluded_domains
-            ).filter(language__isnull=False, branch__releases=self, domain__dtype='ui'
-            ).values('branch_id', 'domain_id', 'language__locale', 'part_po__translated', 'part_po__fuzzy', 
'part_po__untranslated')
+        all_ui_pots = Statistics.objects.select_related(
+            'part_po'
+        ).exclude(
+            domain__in=self.excluded_domains
+        ).filter(language__isnull=True, branch__releases=self, domain__dtype='ui')
+        all_ui_stats = Statistics.objects.select_related(
+            'part_po', 'language'
+        ).exclude(
+            domain__in=self.excluded_domains
+        ).filter(
+            language__isnull=False, branch__releases=self, domain__dtype='ui'
+        ).values('branch_id', 'domain_id', 'language__locale', 'part_po__translated',
+                 'part_po__fuzzy', 'part_po__untranslated')
         stats_d = {
             "%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)
@@ -1028,14 +1054,22 @@ class Release(models.Model):
         """ For partial UI stats, the total number can differ from lang to lang, so we
             are bound to iterate each stats to sum it """
         if all_pots is None:
-            all_pots = Statistics.objects.select_related('part_po'
-                ).exclude(domain__in=self.excluded_domains
-                ).filter(language__isnull=True, branch__releases=self, domain__dtype='ui')
+            all_pots = Statistics.objects.select_related(
+                'part_po'
+            ).exclude(
+                domain__in=self.excluded_domains
+            ).filter(language__isnull=True, branch__releases=self, domain__dtype='ui')
         if all_stats_d is None:
-            all_stats = Statistics.objects.select_related('part_po', 'language'
-                ).exclude(domain__in=self.excluded_domains
-                ).filter(language=lang, branch__releases=self, domain__dtype='ui'
-                ).values('branch_id', 'domain_id', 'language__locale', 'part_po__translated', 
'part_po__fuzzy', 'part_po__untranslated')
+            all_stats = Statistics.objects.select_related(
+                'part_po', 'language'
+            ).exclude(
+                domain__in=self.excluded_domains
+            ).filter(
+                language=lang, branch__releases=self, domain__dtype='ui'
+            ).values(
+                'branch_id', 'domain_id', 'language__locale', 'part_po__translated',
+                'part_po__fuzzy', 'part_po__untranslated'
+            )
             all_stats_d = {
                 "%d-%d-%s" % (st['branch_id'], st['domain_id'], st['language__locale']): sum(filter(
                     None, [st['part_po__translated'], st['part_po__fuzzy'], st['part_po__untranslated']]
@@ -1137,23 +1171,32 @@ class Release(models.Model):
                     stats[locale]['doc']['translated_perc'] = int(100 * row['trans'] / total_docstrings)
                     stats[locale]['doc']['fuzzy_perc'] = int(100 * row['fuzzy'] / total_docstrings)
                     stats[locale]['doc']['untranslated_perc'] = int(
-                        100 * stats[locale]['doc']['untranslated'] / total_docstrings)
+                        100 * stats[locale]['doc']['untranslated'] / total_docstrings
+                    )
             if row['domain__dtype'] == 'ui':
                 stats[locale]['ui']['translated'] = row['trans']
                 stats[locale]['ui']['fuzzy'] = row['fuzzy']
                 stats[locale]['ui']['untranslated'] = total_uistrings - (row['trans'] + row['fuzzy'])
                 stats[locale]['ui_part']['translated'] = row['trans_p']
                 stats[locale]['ui_part']['fuzzy'] = row['fuzzy_p']
-                stats[locale]['ui_part']['untranslated'] = total_uistrings_part[locale] - (row['trans_p'] + 
row['fuzzy_p'])
+                stats[locale]['ui_part']['untranslated'] = total_uistrings_part[locale] - (
+                        row['trans_p'] + row['fuzzy_p']
+                )
                 if total_uistrings > 0:
                     stats[locale]['ui']['translated_perc'] = int(100 * row['trans'] / total_uistrings)
                     stats[locale]['ui']['fuzzy_perc'] = int(100 * row['fuzzy'] / total_uistrings)
                     stats[locale]['ui']['untranslated_perc'] = int(
                         100 * stats[locale]['ui']['untranslated'] / total_uistrings)
                 if total_uistrings_part.get(locale, 0) > 0:
-                    stats[locale]['ui_part']['translated_perc'] = int(100 * row['trans_p'] 
/total_uistrings_part[locale])
-                    stats[locale]['ui_part']['fuzzy_perc'] = int(100 * row['fuzzy_p'] 
/total_uistrings_part[locale])
-                    stats[locale]['ui_part']['untranslated_perc'] = int(100 * 
stats[locale]['ui_part']['untranslated'] / total_uistrings_part[locale])
+                    stats[locale]['ui_part']['translated_perc'] = int(
+                        100 * row['trans_p'] / total_uistrings_part[locale]
+                    )
+                    stats[locale]['ui_part']['fuzzy_perc'] = int(
+                        100 * row['fuzzy_p'] / total_uistrings_part[locale]
+                    )
+                    stats[locale]['ui_part']['untranslated_perc'] = int(
+                        100 * stats[locale]['ui_part']['untranslated'] / total_uistrings_part[locale]
+                    )
 
         results = list(stats.values())
         # Sort by most translated first
@@ -1175,10 +1218,11 @@ class Release(models.Model):
         partial = False
         if dtype == "ui-part":
             dtype, partial = "ui", True
-        pot_stats = Statistics.objects.exclude(domain__in=self.excluded_domains
-            ).filter(language=None, branch__releases=self, domain__dtype=dtype, full_po__isnull=False)
-        po_stats = dict([("%s-%s" % (st.branch_id, st.domain_id), st)
-                         for st in Statistics.objects.filter(language=lang, branch__releases=self, 
domain__dtype=dtype)])
+        pot_stats = Statistics.objects.exclude(
+            domain__in=self.excluded_domains
+        ).filter(language=None, branch__releases=self, domain__dtype=dtype, full_po__isnull=False)
+        po_stats = {f"{st.branch_id}-{st.domain_id}": st
+                    for st in Statistics.objects.filter(language=lang, branch__releases=self, 
domain__dtype=dtype)}
         lang_files = []
         last_modif_date = datetime(1970, 1, 1)
         # Create list of files
@@ -1255,54 +1299,47 @@ class PoFile(models.Model):
     def pot_size(self, words=False):
         if words:
             return self.translated_words + self.fuzzy_words + self.untranslated_words
-        else:
-            return self.translated + self.fuzzy + self.untranslated
+        return self.translated + self.fuzzy + self.untranslated
 
     def fig_count(self):
         """ If stat of a document type, get the number of figures in the document """
-        return self.figures and len(self.figures) or 0
+        return len(self.figures) if self.figures else 0
 
     def tr_percentage(self):
         pot_size = self.pot_size()
         if pot_size == 0:
             return 0
-        else:
-            return int(100*self.translated/pot_size)
+        return int(100 * self.translated / pot_size)
 
     def tr_word_percentage(self):
         pot_size = self.pot_size(words=True)
         if pot_size == 0:
             return 0
-        else:
-            return int(100*self.translated_words/pot_size)
+        return int(100 * self.translated_words / pot_size)
 
     def fu_percentage(self):
         pot_size = self.pot_size()
         if pot_size == 0:
             return 0
-        else:
-            return int(100*self.fuzzy/pot_size)
+        return int(100 * self.fuzzy / pot_size)
 
     def fu_word_percentage(self):
         pot_size = self.pot_size(words=True)
         if pot_size == 0:
             return 0
-        else:
-            return int(100*self.fuzzy_words/pot_size)
+        return int(100 * self.fuzzy_words / pot_size)
 
     def un_percentage(self):
         pot_size = self.pot_size()
         if pot_size == 0:
             return 0
-        else:
-            return int(100*self.untranslated/pot_size)
+        return int(100 * self.untranslated / pot_size)
 
     def un_word_percentage(self):
         pot_size = self.pot_size(words=True)
         if pot_size == 0:
             return 0
-        else:
-            return int(100*self.untranslated_words/pot_size)
+        return int(100 * self.untranslated_words / pot_size)
 
     def update_stats(self):
         stats = utils.po_file_stats(Path(self.full_path))
@@ -1369,48 +1406,47 @@ class Statistics(models.Model):
         """ Returns the type of the domain (ui, docbook, mallard) """
         if self.domain.dtype == "ui":
             return "ui"
-        else:
-            return self.domain.doc_format(self.branch).format
+        return self.domain.doc_format(self.branch).format
 
     def tr_percentage(self, scope='full'):
         if scope == 'full' and self.full_po:
             return self.full_po.tr_percentage()
-        elif scope == 'part' and self.part_po:
+        if scope == 'part' and self.part_po:
             return self.part_po.tr_percentage()
         return 0
 
     def tr_word_percentage(self, scope='full'):
         if scope == 'full' and self.full_po:
             return self.full_po.tr_word_percentage()
-        elif scope == 'part' and self.part_po:
+        if scope == 'part' and self.part_po:
             return self.part_po.tr_word_percentage()
         return 0
 
     def fu_percentage(self, scope='full'):
         if scope == 'full' and self.full_po:
             return self.full_po.fu_percentage()
-        elif scope == 'part' and self.part_po:
+        if scope == 'part' and self.part_po:
             return self.part_po.fu_percentage()
         return 0
 
     def fu_word_percentage(self, scope='full'):
         if scope == 'full' and self.full_po:
             return self.full_po.fu_word_percentage()
-        elif scope == 'part' and self.part_po:
+        if scope == 'part' and self.part_po:
             return self.part_po.fu_word_percentage()
         return 0
 
     def un_percentage(self, scope='full'):
         if scope == 'full' and self.full_po:
             return self.full_po.un_percentage()
-        elif scope == 'part' and self.part_po:
+        if scope == 'part' and self.part_po:
             return self.part_po.un_percentage()
         return 0
 
     def un_word_percentage(self, scope='full'):
         if scope == 'full' and self.full_po:
             return self.full_po.un_word_percentage()
-        elif scope == 'part' and self.part_po:
+        if scope == 'part' and self.part_po:
             return self.part_po.un_word_percentage()
         return 0
 
@@ -1420,8 +1456,7 @@ class Statistics(models.Model):
                 'lang_name': _(self.language.name),
                 'lang_locale': self.language.locale
             }
-        else:
-            return "pot file"
+        return "pot file"
 
     @property
     def module_name(self):
@@ -1436,9 +1471,10 @@ class Statistics(models.Model):
 
     def filename(self, potfile=False, reduced=False):
         if not self.is_pot_stats() and not potfile:
-            return "%s.%s.%s.%spo" % (self.domain.potbase(), self.branch.name_escaped, self.language.locale, 
reduced and "reduced." or "")
-        else:
-            return "%s.%s.%spot" % (self.domain.potbase(), self.branch.name_escaped, reduced and "reduced." 
or "")
+            return "%s.%s.%s.%spo" % (
+                self.domain.potbase(), self.branch.name_escaped, self.language.locale, reduced and 
"reduced." or ""
+            )
+        return "%s.%s.%spot" % (self.domain.potbase(), self.branch.name_escaped, reduced and "reduced." or 
"")
 
     def pot_text(self):
         if not self.full_po:
@@ -1446,7 +1482,7 @@ class Statistics(models.Model):
         pot_size = self.full_po.pot_size()
         pot_words_size = self.full_po.pot_size(words=True)
         fig_count = self.full_po.fig_count()
-        """ Return stat table header: 'POT file (n messages) - updated on ??-??-???? tz' """
+        # 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
@@ -1584,7 +1620,9 @@ class Statistics(models.Model):
                 self.save()
 
             # Try to compute a reduced po file
-            if (self.full_po.fuzzy + self.full_po.untranslated) > 0 and not self.branch.is_archive_only() 
and self.domain.red_filter != '-':
+            if ((self.full_po.fuzzy + self.full_po.untranslated) > 0
+                    and not self.branch.is_archive_only()
+                    and self.domain.red_filter != '-'):
                 # Generate partial_po and store partial stats
                 if self.full_po.path.endswith('.pot'):
                     part_po_path = self.full_po.full_path.with_suffix(".reduced.pot")
@@ -1594,7 +1632,8 @@ class Statistics(models.Model):
                 part_stats = utils.po_file_stats(part_po_path)
                 if (part_stats['translated'] + part_stats['fuzzy'] + part_stats['untranslated'] ==
                         stats['translated'] + stats['fuzzy'] + stats['untranslated']):
-                    # No possible gain, set part_po = full_po so it is possible to compute complete stats at 
database level
+                    # No possible gain, set part_po = full_po so it is possible to
+                    # compute complete stats at database level
                     part_po_equals_full_po()
                     part_po_path.unlink()
                 else:
@@ -1621,7 +1660,7 @@ class Statistics(models.Model):
             for err in fig_errors:
                 self.set_error(*err)
 
-        logger.debug("%s:\n%s" % (self.language, str(stats)))
+        logger.debug("%s:\n%s", self.language, str(stats))
 
     def set_error(self, tp, description):
         Information.objects.create(statistics=self, type=tp, description=description)
@@ -1630,7 +1669,9 @@ class Statistics(models.Model):
         """ Return a message of type 1.'error', or 2.'warn, or 3.'warn """
         error = None
         for e in self.information_set.all():
-            if not error or e.type in ('error', 'error-ext') or (e.type in ('warn','warn-ext') and 
error.type == 'info'):
+            if (not error
+                    or e.type in ('error', 'error-ext')
+                    or (e.type in ('warn', 'warn-ext') and error.type == 'info')):
                 error = e
         return error
 
@@ -1670,6 +1711,7 @@ class Statistics(models.Model):
             }
         """
         # Import here to prevent a circular dependency
+        # pylint: disable=import-outside-toplevel
         from vertimus.models import State, Action
 
         scope = "full"
@@ -1683,18 +1725,24 @@ class Statistics(models.Model):
         # Sorted by module to allow grouping ('fake' stats)
         pot_stats = Statistics.objects.select_related('domain', 'branch__module', 'full_po', 'part_po')
         if release:
-            pot_stats = pot_stats.filter(language=None, branch__releases=release, 
domain__dtype=dtype).order_by('branch__module__name')
-            categ_names = dict([(cat.branch_id, cat.name.name)
-                                for cat in Category.objects.select_related('branch', 
'name').filter(release=release)])
+            pot_stats = pot_stats.filter(
+                language=None, branch__releases=release, domain__dtype=dtype
+            ).order_by('branch__module__name')
+            categ_names = {
+                cat.branch_id: cat.name.name
+                for cat in Category.objects.select_related('branch', 'name').filter(release=release)
+            }
         else:
             pot_stats = pot_stats.filter(language=None, domain__dtype=dtype).order_by('branch__module__name')
 
         tr_stats = Statistics.objects.select_related('domain', 'language', 'branch__module', 'full_po', 
'part_po')
         if release:
-            tr_stats = tr_stats.filter(language=lang, branch__releases=release, 
domain__dtype=dtype).order_by('branch__module__id')
+            tr_stats = tr_stats.filter(
+                language=lang, branch__releases=release, domain__dtype=dtype
+            ).order_by('branch__module__id')
         else:
             tr_stats = tr_stats.filter(language=lang, domain__dtype=dtype).order_by('branch__module__id')
-        tr_stats_dict = dict([("%d-%d" % (st.branch.id, st.domain.id),st) for st in tr_stats])
+        tr_stats_dict = {f"{st.branch_id}-{st.domain_id}": st for st in tr_stats}
 
         infos_dict = Information.get_info_dict(lang)
 
@@ -1704,11 +1752,11 @@ class Statistics(models.Model):
             vt_states = vt_states.filter(language=lang, branch__releases=release, domain__dtype=dtype)
         else:
             vt_states = vt_states.filter(language=lang, domain__dtype=dtype)
-        vt_states_dict = dict([("%d-%d" % (vt.branch.id, vt.domain.id),vt) for vt in vt_states])
+        vt_states_dict = {f"{vt.branch_id}-{vt.domain_id}": vt for vt in vt_states}
 
         # Get comments from last action of State objects
         actions = Action.objects.filter(state_db__in=vt_states, comment__isnull=False).order_by('created')
-        actions_dict = dict([(act.state_db_id, act) for act in actions])
+        actions_dict = {action.state_db_id: action for action in actions}
         for vt_state in vt_states_dict.values():
             if vt_state.id in actions_dict:
                 vt_state.last_comment = actions_dict[vt_state.id].comment
@@ -1719,7 +1767,7 @@ class Statistics(models.Model):
             categ_key = "Default"
             if release:
                 categ_key = categ_names.get(stat.branch_id)
-            domname = stat.domain.description and _(stat.domain.description) or ""
+            domname = _(stat.domain.description) if stat.domain.description else ""
             branchname = stat.branch.name
             module = stat.branch.module
             if categ_key not in stats['categs']:
@@ -1761,8 +1809,12 @@ class Statistics(models.Model):
                 # Here we add the 2nd or more stat to the same module-branch
                 if len(stats['categs'][categ_key]['modules'][module][branchname]) == 2:
                     # Create a fake statistics object for module summary
-                    stats['categs'][categ_key]['modules'][module][branchname][0][1] = 
FakeSummaryStatistics(stat.domain.module, stat.branch, dtype)
-                    
stats['categs'][categ_key]['modules'][module][branchname][0][1].trans(stats['categs'][categ_key]['modules'][module][branchname][1][1])
+                    stats['categs'][categ_key]['modules'][module][branchname][0][1] = FakeSummaryStatistics(
+                        stat.domain.module, stat.branch, dtype
+                    )
+                    stats['categs'][categ_key]['modules'][module][branchname][0][1].trans(
+                        stats['categs'][categ_key]['modules'][module][branchname][1][1]
+                    )
                 stats['categs'][categ_key]['modules'][module][branchname].append([domname, stat])
                 stats['categs'][categ_key]['modules'][module][branchname][0][1].trans(stat)
 
@@ -1773,13 +1825,13 @@ class Statistics(models.Model):
             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 key, categ in stats['categs'].items():
+        for categ in stats['categs'].values():
             categ['cattotal'] = categ['cattrans'] + categ['catfuzzy'] + categ['catuntrans']
             if categ['cattotal'] > 0:
                 categ['cattransperc'] = int(100*categ['cattrans']/categ['cattotal'])
             # Sort domains
             for mod in categ['modules'].values():
-                for branch, doms in mod.items():
+                for doms in mod.values():
                     doms.sort(key=itemgetter(0))
         # Sort errors
         stats['all_errors'].sort()
@@ -1861,20 +1913,17 @@ class FakeSummaryStatistics:
     def tr_percentage(self, scope='full'):
         if self.pot_size() == 0:
             return 0
-        else:
-            return int(100*self._translated/self.pot_size())
+        return int(100 * self._translated / self.pot_size())
 
     def fu_percentage(self, scope='full'):
         if self.pot_size() == 0:
             return 0
-        else:
-            return int(100*self._fuzzy/self.pot_size())
+        return int(100 * self._fuzzy / self.pot_size())
 
     def un_percentage(self, scope='full'):
         if self.pot_size() == 0:
             return 0
-        else:
-            return int(100*self._untranslated/self.pot_size())
+        return int(100*self._untranslated/self.pot_size())
 
 
 class StatisticsArchived(models.Model):
@@ -1891,15 +1940,18 @@ class StatisticsArchived(models.Model):
     class Meta:
         db_table = 'statistics_archived'
 
+
 INFORMATION_TYPE_CHOICES = (
     ('info', 'Information'),
-    ('warn','Warning'),
-    ('error','Error'),
+    ('warn', 'Warning'),
+    ('error', 'Error'),
     # Type of warning/error external to po file itself (LINGUAS, images, etc.)
     # po files containing these are always rechecked
-    ('warn-ext','Warning (external)'),
-    ('error-ext','Error (external)')
+    ('warn-ext', 'Warning (external)'),
+    ('error-ext', 'Error (external)')
 )
+
+
 class Information(models.Model):
     statistics = models.ForeignKey('Statistics', on_delete=models.CASCADE)
     # Priority of a stats message
@@ -1935,7 +1987,7 @@ class Information(models.Model):
 
         text = _(text)
 
-        #FIXME: if multiple substitutions, works only if order of %s is unchanged in translated string
+        #  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)
         return text
@@ -1948,6 +2000,7 @@ class Information(models.Model):
         }
         return link
 
+
 class InformationArchived(models.Model):
     statistics = models.ForeignKey('StatisticsArchived', on_delete=models.CASCADE)
     # Priority of a stats message
@@ -1965,5 +2018,6 @@ def try_int(value):
     except ValueError:
         return value
 
+
 def split_name(value):
     return tuple(try_int(part) for part in re.split(r"[-\.]", value))
diff --git a/stats/tests/tests.py b/stats/tests/tests.py
index 9b35e72a..12d72f2f 100644
--- a/stats/tests/tests.py
+++ b/stats/tests/tests.py
@@ -83,6 +83,14 @@ class ModuleTestCase(TestCase):
         self.assertEqual(response.status_code, 404)
 
     def test_module_bugs_reporting(self):
+        self.assertEqual(
+            self.mod.get_bugs_i18n_url(),
+            'https://gitlab.gnome.org/GNOME/gnome-hello/issues/?state=opened&label_name[]=8.%20Translation'
+        )
+        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'
+        )
         response = self.client.get(reverse('module', args=[self.mod.name]))
         self.assertContains(
             response,


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