Repository: translate/pootle

Issue:
Show stats for disabled projects
Right now, when admin disables a project and then goes to the project browser page, the stats are no longer shown (we only see dashes). There's no reason to do that. We need to display disabled projects to admins only, and show the banner telling that the project is disabled (as we do now), but otherwise we need to render disabled projects in the same way as regular ones, and allow drilling down into translation projects, folders, stores, and even open units for translation.

See my comment that explains how we should treat disabled projects: https://github.com/translate/pootle/issues/3530#issuecomment-132747404



Discussion:
This is not an UI issue: if the data was there it'd be displayed. The problem is these stats are not being calculated, so it's a regression in `refresh_stats_rq`.

Not exactly: the project was there (as an active one), and it had all the stats. Once I marked it as a disabled, all its stats were gone. So it's not about stats not being calculated, it's rather about stats being immediately deleted or simply not displayed.

Yes that's valid as you mentioned in the original report. My point was that missing data is the cause that leads to not displaying these stats.

So in fact, there are several issues here:
1. `refresh_stats_rq` ignoring disabled projects on its processing.
2. Disabling a project wiping out existing stats.

@ta2-1 also mentioned that when disabled projects are processed in `refresh_stats_rq`, stats from stores are not properly populated to TPs.


Resolving patch:
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
@@ -225,6 +225,28 @@ def get_queryset(self):
             )
         )
 
+    def live(self):
+        """Filters non-obsolete units."""
+        return self.filter(state__gt=OBSOLETE)
+
+    def get_for_user(self, user):
+        """Filters units for a specific user.
+
+        - Admins always get all non-obsolete units
+        - Regular users only get units from enabled projects.
+
+        NOTE: This doesn't do any permission checking.
+
+        :param user: The user for whom units need to be retrieved for.
+        :return: A filtered queryset with `Unit`s for `user`.
+        """
+        if user.is_superuser:
+            return self.live()
+
+        return self.live() \
+                   .filter(store__translation_project__project__disabled=False)
+
+
     def get_for_path(self, pootle_path, user):
         """Returns units that fall below the `pootle_path` umbrella.
 
@@ -233,10 +255,7 @@ def get_for_path(self, pootle_path, user):
         """
         lang, proj, dir_path, filename = split_pootle_path(pootle_path)
 
-        units_qs = super(UnitManager, self).get_queryset().filter(
-            state__gt=OBSOLETE,
-            store__translation_project__project__disabled=False,
-        )
+        units_qs = self.get_for_user(user)
 
         # /projects/<project_code>/translate/*
         if lang is None and proj is not None:
diff --git a/pootle/apps/pootle_translationproject/models.py b/pootle/apps/pootle_translationproject/models.py
--- a/pootle/apps/pootle_translationproject/models.py
+++ b/pootle/apps/pootle_translationproject/models.py
@@ -115,14 +115,12 @@ def get_terminology_project(self, language_id):
                         project__checkstyle='terminology')
 
     def live(self):
-        """Filters translation projects that have non-obsolete directories
-        and they belong to enabled projects."""
-        return self.filter(directory__obsolete=False, project__disabled=False)
+        """Filters translation projects that have non-obsolete directories."""
+        return self.filter(directory__obsolete=False)
 
     def disabled(self):
-        """Filters translation projects that have obsolete directories or they
-        belong to disabled projects."""
-        return self.filter(Q(directory__obsolete=True) | Q(project__disabled=True))
+        """Filters translation projects that belong to disabled projects."""
+        return self.filter(project__disabled=True)
 
     def for_user(self, user):
         """Filters translation projects for a specific user.
@@ -135,9 +133,28 @@ def for_user(self, user):
         :return: A filtered queryset with `TranslationProject`s for `user`.
         """
         if user.is_superuser:
-            return self.all()
+            return self.live()
+
+        return self.live().filter(project__disabled=False)
+
+    def get_for_user(self, user, project_code, language_code):
+        """Gets a `language_code`/`project_code` translation project
+        for a specific `user`.
+
+        - Admins can get the translation project even
+            if its project is disabled.
+        - Regular users only get a translation project
+            if its project isn't disabled.
+
+        :param user: The user for whom the translation project needs
+            to be retrieved.
+        :param project_code: The code of a project for the TP to retrieve.
+        :param language_code: The code of the language fro the TP to retrieve.
+        :return: The `TranslationProject` matching the params, raises otherwise.
+        """
+        return self.for_user(user).get(project__code=project_code,
+                                       language__code=language_code)
 
-        return self.live()
 
 
 class TranslationProject(models.Model, CachedTreeItem):
@@ -228,7 +245,7 @@ def units(self):
 
     @property
     def disabled(self):
-        return self.directory.obsolete or self.project.disabled
+        return self.project.disabled
 
     @property
     def is_terminology_project(self):
diff --git a/pootle/core/decorators.py b/pootle/core/decorators.py
--- a/pootle/core/decorators.py
+++ b/pootle/core/decorators.py
@@ -62,9 +62,10 @@ def wrapped(request, *args, **kwargs):
 
         if language_code and project_code:
             try:
-                path_obj = TranslationProject.objects.live().get(
-                    language__code=language_code,
-                    project__code=project_code,
+                path_obj = TranslationProject.objects.get_for_user(
+                    user=request.user,
+                    language_code=language_code,
+                    project_code=project_code,
                 )
             except TranslationProject.DoesNotExist:
                 path_obj = None


Test patch: