Repository: translate/pootle

Issue:
Language dropdown not showing template languages
_Version: 2.5.1_

The language dropdown menu is only displaying languages that are _not_ specified as source language in any project.

Deactivating this filter shows all possible languages and does not seem to have any undesirable side effects:

``` diff
diff --git a/pootle/apps/pootle_language/models.py b/pootle/apps/pootle_language/models.py
index 03dbd96..9611602 100644
--- a/pootle/apps/pootle_language/models.py
+++ b/pootle/apps/pootle_language/models.py
@@ -56,7 +56,6 @@ class LiveLanguageManager(models.Manager):
         return super(LiveLanguageManager, self).get_query_set().filter(
                 ~models.Q(code='templates'),
                 translationproject__isnull=False,
-                project__isnull=True,
             ).distinct()
```



Discussion:
Just a note for the record: If you have 6 projects, and one of them uses German as the source language, the German language is not shown on the language dropdown never.

May be this was intended to hide the special “Templates” language?

If you check the code included in the bug description you can see that the “Templates” language is hidden, but using a different filter.

@julen is that what you guys just fixed?

We removed the explicit check for `templates`, this talks about source languages in general. Feel free to check it out and close if it doesn't apply anymore.

`project__isnull=True` was there to avoid routing users to the English source language, which leads to a misleading empty page in our case and the vast majority of deployments, but it's not always the desired behavior as shown by the OP.

In any case the actual fix is not _just_ taking that line out, but taking out languages with no live TPs. As described in the manager's docstring: _A live language is any language containing at least a project with translatable files._ The pushed commit is a regression in this aspect, hence reopening.

Just for the record, commit 21b59033 has been reverted in 9387c920. So this issue is still not fixed as told in the previous commit.

There is agreement on including all template languages on the dropdown, but instead hide any language without visible TPs.


Resolving patch:
diff --git a/pootle/apps/pootle_app/views/index/index.py b/pootle/apps/pootle_app/views/index/index.py
--- a/pootle/apps/pootle_app/views/index/index.py
+++ b/pootle/apps/pootle_app/views/index/index.py
@@ -28,7 +28,9 @@ def view(request):
     lang = request.COOKIES.get(COOKIE_NAME, None)
 
     if lang is None:
-        supported = Language.live.cached_dict()
+        supported = Language.live.cached_dict(
+            show_all=request.user.is_superuser
+        )
         lang = get_lang_from_http_header(request, supported)
 
     if lang is not None and lang not in ('projects', ''):
diff --git a/pootle/apps/pootle_language/models.py b/pootle/apps/pootle_language/models.py
--- a/pootle/apps/pootle_language/models.py
+++ b/pootle/apps/pootle_language/models.py
@@ -31,24 +31,41 @@ class LiveLanguageManager(models.Manager):
     """
 
     def get_queryset(self):
+        """Returns a queryset for all live languages for enabled projects."""
         return super(LiveLanguageManager, self).get_queryset().filter(
             translationproject__isnull=False,
-            project__isnull=True,
+            translationproject__directory__obsolete=False,
+            translationproject__project__disabled=False,
         ).distinct()
 
-    def cached_dict(self, locale_code='en-us'):
+    def get_all_queryset(self):
+        """Returns a queryset for all live languages for all projects."""
+        return super(LiveLanguageManager, self).get_queryset().filter(
+            translationproject__isnull=False,
+            translationproject__directory__obsolete=False,
+        ).distinct()
+
+    def cached_dict(self, locale_code='en-us', show_all=False):
         """Retrieves a sorted list of live language codes and names.
 
+        By default only returns live languages for enabled projects, but it can
+        also return live languages for disabled projects if specified.
+
         :param locale_code: the UI locale for which language full names need to
             be localized.
+        :param show_all: tells whether to return all live languages (both for
+            disabled and enabled projects) or only live languages for enabled
+            projects.
         :return: an `OrderedDict`
         """
-        key = make_method_key(self, 'cached_dict', locale_code)
+        key_prefix = 'all_cached_dict' if show_all else 'cached_dict'
+        key = make_method_key(self, key_prefix, locale_code)
         languages = cache.get(key, None)
         if languages is None:
+            qs = self.get_all_queryset() if show_all else self.get_queryset()
             languages = OrderedDict(
                 sorted([(lang[0], tr_lang(lang[1]))
-                        for lang in self.values_list('code', 'fullname')],
+                        for lang in qs.values_list('code', 'fullname')],
                        cmp=locale.strcoll,
                        key=lambda x: x[1])
             )
diff --git a/pootle/apps/pootle_misc/context_processors.py b/pootle/apps/pootle_misc/context_processors.py
--- a/pootle/apps/pootle_misc/context_processors.py
+++ b/pootle/apps/pootle_misc/context_processors.py
@@ -53,7 +53,8 @@ def pootle_context(request):
             'DEBUG': settings.DEBUG,
         },
         'custom': settings.POOTLE_CUSTOM_TEMPLATE_CONTEXT,
-        'ALL_LANGUAGES': Language.live.cached_dict(translation.get_language()),
+        'ALL_LANGUAGES': Language.live.cached_dict(translation.get_language(),
+                                                   request.user.is_superuser),
         'ALL_PROJECTS': Project.objects.cached_dict(request.user),
         'SOCIAL_AUTH_PROVIDERS': _get_social_auth_providers(request),
         'display_agreement': _agreement_context(request),


Test patch:
diff --git a/tests/models/language.py b/tests/models/language.py
--- a/tests/models/language.py
+++ b/tests/models/language.py
@@ -8,6 +8,9 @@
 
 import pytest
 
+from pytest_pootle.factories import (LanguageDBFactory, ProjectDBFactory,
+                                     TranslationProjectFactory)
+
 from pootle_language.models import Language
 
 
@@ -17,3 +20,92 @@ def test_language_repr():
     assert (
         "<Language: %s>" % language.name
         == repr(language))
+
+
+@pytest.mark.django_db
+def test_language_only_in_disabled_proj_liveness(templates, project0,
+                                                 templates_project0):
+    project0.disabled = True
+    project0.save()
+
+    assert project0.disabled
+    assert templates.translationproject_set.count() == 1
+    assert templates not in Language.live.all()
+    assert templates in Language.live.get_all_queryset()
+
+
+@pytest.mark.django_db
+def test_language_liveness():
+    language = LanguageDBFactory()
+
+    # Test unused language is not live.
+    assert language.translationproject_set.count() == 0
+    assert language not in Language.live.all()
+    assert language not in Language.live.get_all_queryset()
+
+    # Create obsolete TP.
+    obsolete_tp = TranslationProjectFactory(
+        language=language,
+        project=ProjectDBFactory(source_language=language)
+    )
+    obsolete_tp.directory.makeobsolete()
+
+    # Test language used only in obsolete TP is not live.
+    assert obsolete_tp.directory.obsolete
+    assert language.translationproject_set.count() == 1
+    assert language not in Language.live.all()
+    assert language not in Language.live.get_all_queryset()
+
+    # Create TP for disabled project.
+    disabled_project = ProjectDBFactory(source_language=language)
+    disabled_project.disabled = True
+    disabled_project.save()
+    disabled_project_tp = TranslationProjectFactory(language=language,
+                                                    project=disabled_project)
+
+    # Test language used only in disabled project and obsolete TP is not live.
+    assert disabled_project.disabled
+    assert language.translationproject_set.count() == 2
+    assert language not in Language.live.all()
+    # But live for admins.
+    assert language in Language.live.get_all_queryset()
+
+    # Create regular TP.
+    TranslationProjectFactory(
+        language=language,
+        project=ProjectDBFactory(source_language=language)
+    )
+
+    # Test language used in regular and obsolete TPs and in TP on disabled
+    # project is live.
+    assert language.translationproject_set.count() == 3
+    assert language in Language.live.all()
+    # But live for admins.
+    assert language in Language.live.get_all_queryset()
+
+    # Delete TP for disabled project.
+    disabled_project_tp.delete()
+
+    # Test language both in obsolete and regular TP is live.
+    assert language.translationproject_set.count() == 2
+    assert language in Language.live.all()
+    assert language in Language.live.get_all_queryset()
+
+    # Create again TP for disabled project and delete obsolete TP.
+    # Delete obsolete TP.
+    disabled_project_tp = TranslationProjectFactory(language=language,
+                                                    project=disabled_project)
+    obsolete_tp.delete()
+
+    # Test language both in disabled project and regular TP is live.
+    assert language.translationproject_set.count() == 2
+    assert language in Language.live.all()
+    assert language in Language.live.get_all_queryset()
+
+    # Delete TP for disabled project.
+    disabled_project_tp.delete()
+
+    # Test templates language is live.
+    assert language.translationproject_set.count() == 1
+    assert language in Language.live.all()
+    assert language in Language.live.get_all_queryset()