Repository: translate/pootle

Issue:
Allow to run refresh_scores by project
Allow to run `refresh_scores` command by project so it is easier/faster to debug.


Discussion:
This means changing the command to extend `PootleCommand` so projects and languages can be specified in order to restrict calculation to specific projecs, languages or TPs.  

Resolving patch:
diff --git a/pootle/apps/pootle_app/management/commands/refresh_scores.py b/pootle/apps/pootle_app/management/commands/refresh_scores.py
--- a/pootle/apps/pootle_app/management/commands/refresh_scores.py
+++ b/pootle/apps/pootle_app/management/commands/refresh_scores.py
@@ -10,15 +10,18 @@
 os.environ['DJANGO_SETTINGS_MODULE'] = 'pootle.settings'
 
 from django.contrib.auth import get_user_model
-from django.core.management.base import BaseCommand
 
 from pootle.core.delegate import score_updater
+from pootle_translationproject.models import TranslationProject
 
+from . import PootleCommand
 
-class Command(BaseCommand):
+
+class Command(PootleCommand):
     help = "Refresh score"
 
     def add_arguments(self, parser):
+        super(Command, self).add_arguments(parser)
         parser.add_argument(
             '--reset',
             action='store_true',
@@ -33,13 +36,27 @@ def add_arguments(self, parser):
             help='User to refresh',
         )
 
-    def handle(self, **options):
-        users = (
+    def get_users(self, **options):
+        return (
             list(get_user_model().objects.filter(
                 username__in=options["users"]).values_list("pk", flat=True))
             if options["users"]
             else None)
+
+    def handle_all_stores(self, translation_project, **options):
+        users = self.get_users(**options)
+        updater = score_updater.get(TranslationProject)(translation_project)
         if options["reset"]:
-            score_updater.get(get_user_model())(users=users).clear()
-            return
-        score_updater.get(get_user_model())().refresh_scores(users)
+            updater.clear(users)
+        else:
+            updater.refresh_scores(users)
+
+    def handle_all(self, **options):
+        if not self.projects and not self.languages:
+            users = self.get_users(**options)
+            if options["reset"]:
+                score_updater.get(get_user_model())(users=users).clear()
+            else:
+                score_updater.get(get_user_model())().refresh_scores(users)
+        else:
+            super(Command, self).handle_all(**options)
diff --git a/pootle/apps/pootle_score/updater.py b/pootle/apps/pootle_score/updater.py
--- a/pootle/apps/pootle_score/updater.py
+++ b/pootle/apps/pootle_score/updater.py
@@ -327,6 +327,22 @@ def calculate(self, start=None, end=None, users=None):
                     reviewed=Sum("reviewed"),
                     suggested=Sum("suggested"))
 
+    def clear(self, users=None):
+        tp_scores = self.score_model.objects.all()
+        store_scores = self.store_score_model.objects.all()
+        user_scores = self.user_score_model.objects.all()
+        if users:
+            tp_scores = tp_scores.filter(user_id__in=users)
+            store_scores = store_scores.filter(user_id__in=users)
+            user_scores = user_scores.filter(id__in=users)
+        if self.tp:
+            tp_scores = tp_scores.filter(tp=self.tp)
+            store_scores = store_scores.filter(
+                store__translation_project=self.tp)
+        tp_scores.delete()
+        store_scores.delete()
+        user_scores.update(score=0)
+
     def refresh_scores(self, users=None, existing=None, existing_tps=None):
         suppress_tp_scores = keep_data(
             signals=(update_scores, ),


Test patch:
diff --git a/tests/commands/refresh_scores.py b/tests/commands/refresh_scores.py
--- a/tests/commands/refresh_scores.py
+++ b/tests/commands/refresh_scores.py
@@ -8,10 +8,11 @@
 
 import pytest
 
+from django.contrib.auth import get_user_model
 from django.core.management import call_command
 
 from pootle.core.contextmanagers import keep_data
-from pootle.core.delegate import review
+from pootle.core.delegate import review, score_updater
 
 
 @pytest.mark.cmd
@@ -90,3 +91,281 @@ def test_refresh_scores_reset(capfd, admin, member):
     assert admin.score == 0
     assert admin.scores.count() == 0
     assert admin.store_scores.count() == 0
+
+
+@pytest.mark.cmd
+@pytest.mark.django_db
+def test_refresh_scores_project(capfd, admin, member, project0):
+    """Reset and set again scores for a project."""
+    member.refresh_from_db()
+    admin.refresh_from_db()
+    updater = score_updater.get(get_user_model())()
+    member_score = updater.calculate(users=[member]).first()[1]
+    admin_score = updater.calculate(users=[admin]).first()[1]
+    member_scores_count = member.scores.filter(tp__project=project0).count()
+    admin_scores_count = admin.scores.filter(tp__project=project0).count()
+    member_store_scores_count = member.store_scores.filter(
+        store__translation_project__project=project0).count()
+    admin_store_scores_count = admin.store_scores.filter(
+        store__translation_project__project=project0).count()
+
+    call_command('refresh_scores', '--reset', '--project=project0')
+    out, err = capfd.readouterr()
+    member.refresh_from_db()
+    admin.refresh_from_db()
+    assert member.score == 0
+    assert member.scores.filter(tp__project=project0).count() == 0
+    assert member.store_scores.filter(
+        store__translation_project__project=project0).count() == 0
+    assert admin.score == 0
+    assert admin.scores.filter(tp__project=project0).count() == 0
+    assert admin.store_scores.filter(
+        store__translation_project__project=project0).count() == 0
+
+    call_command('refresh_scores', '--project=project0')
+    out, err = capfd.readouterr()
+    member.refresh_from_db()
+    admin.refresh_from_db()
+    assert round(member.score, 2) == round(member_score, 2)
+    assert (member.scores.filter(tp__project=project0).count() ==
+            member_scores_count)
+    assert (member.store_scores.filter(
+        store__translation_project__project=project0).count() ==
+        member_store_scores_count)
+    assert round(admin.score, 2) == round(admin_score, 2)
+    assert (admin.scores.filter(tp__project=project0).count() ==
+            admin_scores_count)
+    assert (admin.store_scores.filter(
+        store__translation_project__project=project0).count() ==
+        admin_store_scores_count)
+
+
+@pytest.mark.cmd
+@pytest.mark.django_db
+def test_refresh_scores_language(capfd, admin, member, language0):
+    """Reset and set again scores for a language."""
+    member.refresh_from_db()
+    admin.refresh_from_db()
+    updater = score_updater.get(get_user_model())()
+    member_score = updater.calculate(users=[member]).first()[1]
+    admin_score = updater.calculate(users=[admin]).first()[1]
+    member_scores_count = member.scores.filter(tp__language=language0).count()
+    admin_scores_count = admin.scores.filter(tp__language=language0).count()
+    member_store_scores_count = member.store_scores.filter(
+        store__translation_project__language=language0).count()
+    admin_store_scores_count = admin.store_scores.filter(
+        store__translation_project__language=language0).count()
+
+    call_command('refresh_scores', '--reset', '--language=language0')
+    out, err = capfd.readouterr()
+    member.refresh_from_db()
+    admin.refresh_from_db()
+    assert member.score == 0
+    assert member.scores.filter(
+        tp__language=language0,
+        tp__project__disabled=False).count() == 0
+    assert member.store_scores.filter(
+        store__translation_project__language=language0,
+        store__translation_project__project__disabled=False).count() == 0
+    assert admin.score == 0
+    assert admin.scores.filter(
+        tp__language=language0,
+        tp__project__disabled=False).count() == 0
+    assert admin.store_scores.filter(
+        store__translation_project__language=language0,
+        store__translation_project__project__disabled=False).count() == 0
+
+    call_command('refresh_scores', '--language=language0')
+    out, err = capfd.readouterr()
+    member.refresh_from_db()
+    admin.refresh_from_db()
+    assert round(member.score, 2) == round(member_score, 2)
+    assert (member.scores.filter(tp__language=language0).count() ==
+            member_scores_count)
+    assert (member.store_scores.filter(
+        store__translation_project__language=language0).count() ==
+        member_store_scores_count)
+    assert round(admin.score, 2) == round(admin_score, 2)
+    assert (admin.scores.filter(tp__language=language0).count() ==
+            admin_scores_count)
+    assert (admin.store_scores.filter(
+        store__translation_project__language=language0).count() ==
+        admin_store_scores_count)
+
+
+@pytest.mark.cmd
+@pytest.mark.django_db
+def test_refresh_scores_reset_tp(capfd, admin, member, tp0):
+    """Reset and set again scores for a TP."""
+    member.refresh_from_db()
+    admin.refresh_from_db()
+    updater = score_updater.get(get_user_model())()
+    member_score = updater.calculate(users=[member]).first()[1]
+    admin_score = updater.calculate(users=[admin]).first()[1]
+    member_scores_count = member.scores.filter(tp=tp0).count()
+    admin_scores_count = admin.scores.filter(tp=tp0).count()
+    member_store_scores_count = member.store_scores.filter(
+        store__translation_project=tp0).count()
+    admin_store_scores_count = admin.store_scores.filter(
+        store__translation_project=tp0).count()
+
+    call_command('refresh_scores', '--reset', '--project=project0',
+                 '--language=language0')
+    out, err = capfd.readouterr()
+    member.refresh_from_db()
+    admin.refresh_from_db()
+    assert member.score == 0
+    assert member.scores.filter(tp=tp0).count() == 0
+    assert member.store_scores.filter(
+        store__translation_project=tp0).count() == 0
+    assert admin.score == 0
+    assert admin.scores.filter(tp=tp0).count() == 0
+    assert admin.store_scores.filter(
+        store__translation_project=tp0).count() == 0
+
+    call_command('refresh_scores', '--project=project0',
+                 '--language=language0')
+    out, err = capfd.readouterr()
+    member.refresh_from_db()
+    admin.refresh_from_db()
+    assert round(member.score, 2) == round(member_score, 2)
+    assert member.scores.filter(tp=tp0).count() == member_scores_count
+    assert (member.store_scores.filter(
+        store__translation_project=tp0).count() ==
+        member_store_scores_count)
+    assert round(admin.score, 2) == round(admin_score, 2)
+    assert admin.scores.filter(tp=tp0).count() == admin_scores_count
+    assert (admin.store_scores.filter(
+        store__translation_project=tp0).count() ==
+        admin_store_scores_count)
+
+
+@pytest.mark.cmd
+@pytest.mark.django_db
+def test_refresh_scores_user_project(capfd, admin, member, project0):
+    """Reset and set again scores for particular user in project."""
+    member.refresh_from_db()
+    admin.refresh_from_db()
+    updater = score_updater.get(get_user_model())()
+    member_score = updater.calculate(users=[member]).first()[1]
+    admin_score = admin.score
+    member_scores_count = member.scores.filter(tp__project=project0).count()
+    admin_scores_count = admin.scores.count()
+    member_store_scores_count = member.store_scores.filter(
+        store__translation_project__project=project0).count()
+    admin_store_scores_count = admin.store_scores.count()
+
+    call_command('refresh_scores', '--reset', '--user=member',
+                 '--project=project0')
+    out, err = capfd.readouterr()
+    member.refresh_from_db()
+    admin.refresh_from_db()
+    assert member.score == 0
+    assert member.scores.filter(tp__project=project0).count() == 0
+    assert member.store_scores.filter(
+        store__translation_project__project=project0).count() == 0
+    assert admin.score == admin_score
+    assert admin.scores.count() == admin_scores_count
+    assert admin.store_scores.count() == admin_store_scores_count
+
+    call_command('refresh_scores', '--user=member', '--project=project0')
+    out, err = capfd.readouterr()
+    member.refresh_from_db()
+    admin.refresh_from_db()
+    assert round(member.score, 2) == round(member_score, 2)
+    assert (member.scores.filter(tp__project=project0).count() ==
+            member_scores_count)
+    assert (member.store_scores.filter(
+        store__translation_project__project=project0).count() ==
+        member_store_scores_count)
+    assert round(admin.score, 2) == round(admin_score, 2)
+
+
+@pytest.mark.cmd
+@pytest.mark.django_db
+def test_refresh_scores_user_language(capfd, admin, member, language0):
+    """Reset and set again scores for particular user in language."""
+    member.refresh_from_db()
+    admin.refresh_from_db()
+    updater = score_updater.get(get_user_model())()
+    member_score = updater.calculate(users=[member]).first()[1]
+    admin_score = admin.score
+    member_scores_count = member.scores.filter(
+        tp__language=language0,
+        tp__project__disabled=False).count()
+    admin_scores_count = admin.scores.count()
+    member_store_scores_count = member.store_scores.filter(
+        store__translation_project__language=language0,
+        store__translation_project__project__disabled=False).count()
+    admin_store_scores_count = admin.store_scores.count()
+
+    call_command('refresh_scores', '--reset', '--user=member',
+                 '--language=language0')
+    out, err = capfd.readouterr()
+    member.refresh_from_db()
+    admin.refresh_from_db()
+    assert member.score == 0
+    assert member.scores.filter(
+        tp__language=language0,
+        tp__project__disabled=False).count() == 0
+    assert member.store_scores.filter(
+        store__translation_project__language=language0,
+        store__translation_project__project__disabled=False).count() == 0
+    assert admin.score == admin_score
+    assert admin.scores.count() == admin_scores_count
+    assert admin.store_scores.count() == admin_store_scores_count
+
+    call_command('refresh_scores', '--user=member', '--language=language0')
+    out, err = capfd.readouterr()
+    member.refresh_from_db()
+    admin.refresh_from_db()
+    assert round(member.score, 2) == round(member_score, 2)
+    assert (member.scores.filter(
+        tp__language=language0,
+        tp__project__disabled=False).count() == member_scores_count)
+    assert (member.store_scores.filter(
+        store__translation_project__language=language0,
+        store__translation_project__project__disabled=False).count() ==
+        member_store_scores_count)
+    assert round(admin.score, 2) == round(admin_score, 2)
+
+
+@pytest.mark.cmd
+@pytest.mark.django_db
+def test_refresh_scores_user_tp(capfd, admin, member, tp0):
+    """Reset and set again scores for particular user in TP."""
+    member.refresh_from_db()
+    admin.refresh_from_db()
+    updater = score_updater.get(get_user_model())()
+    member_score = updater.calculate(users=[member]).first()[1]
+    admin_score = admin.score
+    member_scores_count = member.scores.filter(tp=tp0).count()
+    admin_scores_count = admin.scores.count()
+    member_store_scores_count = member.store_scores.filter(
+        store__translation_project=tp0).count()
+    admin_store_scores_count = admin.store_scores.count()
+
+    call_command('refresh_scores', '--reset', '--user=member',
+                 '--project=project0', '--language=language0')
+    out, err = capfd.readouterr()
+    member.refresh_from_db()
+    admin.refresh_from_db()
+    assert member.score == 0
+    assert member.scores.filter(tp=tp0).count() == 0
+    assert member.store_scores.filter(
+        store__translation_project=tp0).count() == 0
+    assert admin.score == admin_score
+    assert admin.scores.count() == admin_scores_count
+    assert admin.store_scores.count() == admin_store_scores_count
+
+    call_command('refresh_scores', '--user=member', '--project=project0',
+                 '--language=language0')
+    out, err = capfd.readouterr()
+    member.refresh_from_db()
+    admin.refresh_from_db()
+    assert round(member.score, 2) == round(member_score, 2)
+    assert member.scores.filter(tp=tp0).count() == member_scores_count
+    assert (member.store_scores.filter(
+        store__translation_project=tp0).count() ==
+        member_store_scores_count)
+    assert round(admin.score, 2) == round(admin_score, 2)