Repository: translate/pootle

Issue:
Custom POOTLE_QUALITY_CHECKER created without any configuration
The issue is that when I write a custom quality checker and set the class into `POOTLE_QUALITY_CHECKER` my checks get called with no language set. This makes writing language dependent checks impossible.

I have tracked this issue down a bit, but I am not a django coder so this is a bit hack and slash.

It appears to be that the quality checker is being created via
  `apps/pootle_misc/checks.py:get_checker`

`get_checker` has this code:

``` python
    if settings.POOTLE_QUALITY_CHECKER:
        return import_func(settings.POOTLE_QUALITY_CHECKER)()
```

So you can see that the quality checker has no parameters specified, resulting in an empty configuration with no language code. 

`get_checker` comes from the `apps/pootle_store/models.py` path.  There is another nicer code path that results in a `TeeChecker` and a much better set of configuration happening in `apps/pootle_translationproject/models.py` for for some reason my install is not using that code path at all.



Discussion:
Potentially naive workaround:

``` diff
--- a/apps/pootle_misc/checks.py 2016-09-07 08:06:21.796150445 -0500
+++ b/apps/pootle_misc/checks.py    2016-09-07 08:06:38.644149853 -0500
@@ -239,10 +239,14 @@

 def get_checker(unit):
     if settings.POOTLE_QUALITY_CHECKER:
-        return import_func(settings.POOTLE_QUALITY_CHECKER)()
-
-    return unit.store.translation_project.checker
+        checkerclasses = [import_func(settings.POOTLE_QUALITY_CHECKER)]
+    else:
+        checkerclasses = [unit.store.translation_project.checker]

+    return checks.TeeChecker(checkerclasses=checkerclasses,
+                             excludefilters=excluded_filters,
+                             errorhandler=None,
+                             languagecode=unit.store.translation_project.language.code)

 def get_category_id(code):
     return CATEGORY_IDS.get(code)
```

Hey @AricStewart, thanks for bringing this up. I had exactly the need you describe a few months ago 😎.

The sad truth is the situation for quality checks is quite messy at the moment, and there are [other issues](https://github.com/translate/pootle/issues/4553) with it too.

Still, what the first code snippet you pasted does is just instantiating an object of the checker class of your choice. Further arguments are passed via the `run_filters` method. So you can currently make your custom checker use a language and other unit-related information by making sure `run_filters` matches your needs.

I suggest you [check the example from `ENChecker`](https://github.com/translate/pootle/blob/e35c2495e88727f922d4bdfd51ad46902c0d4314/pootle/apps/pootle_misc/checks.py#L278-L291), which inherits from `checks.UnitChecker`. Note `UnitChecker.run_filters` will go through all existing quality checks and run them by making use of the `run_test` method. This probably gives you enough to get started.

That snippet goes the right way, but `unit.store.translation_project.checker` is already a `TeeChecker`, so better:

``` diff
diff --git a/pootle/apps/pootle_misc/checks.py b/pootle/apps/pootle_misc/checks.py
index 852d31d..aca1c82 100644
--- a/pootle/apps/pootle_misc/checks.py
+++ b/pootle/apps/pootle_misc/checks.py
@@ -250,9 +250,19 @@ def clean_plurr_placeholder(string):
     return plurr_plural_suffix_regex.sub('', string)


+def filtererrorhandler(functionname, str1, str2, e):
+    logging.error(u"Error in filter %s: %r, %r, %s", functionname, str1, str2,
+                  e)
+    return False
+
+
 def get_checker(unit):
     if settings.POOTLE_QUALITY_CHECKER:
-        return import_func(settings.POOTLE_QUALITY_CHECKER)()
+        checkerclasses = [import_func(settings.POOTLE_QUALITY_CHECKER)]
+        return checks.TeeChecker(checkerclasses=checkerclasses,
+                                 excludefilters=excluded_filters,
+                                 errorhandler=filtererrorhandler,
+                                 languagecode=unit.store.translation_project.language.code)

     return unit.store.translation_project.checker

diff --git a/pootle/apps/pootle_translationproject/models.py b/pootle/apps/pootle_translationproject/models.py
index 12d2214..a66ab76 100644
--- a/pootle/apps/pootle_translationproject/models.py
+++ b/pootle/apps/pootle_translationproject/models.py
@@ -209,6 +209,8 @@ class TranslationProject(models.Model, CachedTreeItem):
     @property
     def checker(self):
         from translate.filters import checks
+        from pootle_misc.checks import filtererrorhandler
+
         # We do not use default Translate Toolkit checkers; instead use
         # our own one
         if settings.POOTLE_QUALITY_CHECKER:
@@ -222,7 +224,7 @@ class TranslationProject(models.Model, CachedTreeItem):

         return checks.TeeChecker(checkerclasses=checkerclasses,
                                  excludefilters=excluded_filters,
-                                 errorhandler=self.filtererrorhandler,
+                                 errorhandler=filtererrorhandler,
                                  languagecode=self.language.code)

     @property
@@ -292,11 +294,6 @@ class TranslationProject(models.Model, CachedTreeItem):
         """Return the related announcement, if any."""
         return StaticPage.get_announcement_for(self.pootle_path, user)

-    def filtererrorhandler(self, functionname, str1, str2, e):
-        logging.error(u"Error in filter %s: %r, %r, %s", functionname, str1,
-                      str2, e)
-        return False
-
     def is_accessible_by(self, user):
         """Returns `True` if the current translation project is accessible
         by `user`.
```

@julen Instead of this being set in the checker itself, the config should be passed by Pootle when instantiating it.

Perhaps even simpler:

``` diff
diff --git a/pootle/apps/pootle_misc/checks.py b/pootle/apps/pootle_misc/checks.
index 852d31d..ff8a155 100644
--- a/pootle/apps/pootle_misc/checks.py
+++ b/pootle/apps/pootle_misc/checks.py
@@ -251,9 +251,6 @@ def clean_plurr_placeholder(string):


 def get_checker(unit):
-    if settings.POOTLE_QUALITY_CHECKER:
-        return import_func(settings.POOTLE_QUALITY_CHECKER)()
-
     return unit.store.translation_project.checker



```

Another problematic instance of this is https://github.com/translate/pootle/blob/42ac249a6372f37fb71a3137b96eb9e3f0ace7a8/pootle/core/checks/checker.py#L218-L229


Resolving patch:
diff --git a/pootle/apps/pootle_misc/checks.py b/pootle/apps/pootle_misc/checks.py
--- a/pootle/apps/pootle_misc/checks.py
+++ b/pootle/apps/pootle_misc/checks.py
@@ -250,13 +250,6 @@ def clean_plurr_placeholder(string):
     return plurr_plural_suffix_regex.sub('', string)
 
 
-def get_checker(unit):
-    if settings.POOTLE_QUALITY_CHECKER:
-        return import_func(settings.POOTLE_QUALITY_CHECKER)()
-
-    return unit.store.translation_project.checker
-
-
 def get_category_id(code):
     return CATEGORY_IDS.get(code)
 
diff --git a/pootle/apps/pootle_store/models.py b/pootle/apps/pootle_store/models.py
--- a/pootle/apps/pootle_store/models.py
+++ b/pootle/apps/pootle_store/models.py
@@ -47,7 +47,7 @@
 from pootle.core.utils.aggregate import max_column
 from pootle.core.utils.timezone import datetime_min, make_aware
 from pootle_format.models import Format
-from pootle_misc.checks import check_names, get_checker
+from pootle_misc.checks import check_names
 from pootle_misc.util import import_func
 from pootle_statistics.models import (Submission, SubmissionFields,
                                       SubmissionTypes)
@@ -664,7 +664,7 @@ def update_qualitychecks(self, keep_false_positives=False):
 
             return False
 
-        checker = get_checker(self)
+        checker = self.store.translation_project.checker
         qc_failures = checker.run_filters(self, categorised=True)
         checks_to_add = []
         for name in qc_failures.iterkeys():
diff --git a/pootle/core/checks/checker.py b/pootle/core/checks/checker.py
--- a/pootle/core/checks/checker.py
+++ b/pootle/core/checks/checker.py
@@ -9,14 +9,12 @@
 import logging
 import time
 
-from django.conf import settings
 from django.utils import timezone
 from django.utils.functional import cached_property
 from django.utils.lru_cache import lru_cache
 
 from pootle.core.mixins.treeitem import CachedMethods
 from pootle_misc.checks import run_given_filters
-from pootle_misc.util import import_func
 from pootle_store.constants import OBSOLETE
 from pootle_store.models import QualityCheck, Store, Unit
 from pootle_store.unit import UnitProxy
@@ -220,12 +218,11 @@ def get_checker(self, tp_pk):
         """Return the site QualityChecker or the QualityCheck associated with
         the a Unit's TP otherwise.
         """
-        if settings.POOTLE_QUALITY_CHECKER:
-            return import_func(settings.POOTLE_QUALITY_CHECKER)()
         try:
             return TranslationProject.objects.get(id=tp_pk).checker
         except TranslationProject.DoesNotExist:
             # There seems to be a risk of dangling Stores with no TP
+            logger.error("Missing TP (pk '%s'). No checker retrieved.", tp_pk)
             return None
 
     def expire_store_cache(self, store_pk=None):


Test patch: