Repository: django-cms/django-cms

Issue:
delete language does not work if language is no longer in settings
The "delete language" function on the _Page_ menu of the Toolbar appears for all languages there is content for (even if the language is not configured in settings). But choosing to delete that language causes a (silent) 500 error.

Design decision:
- not display any languages that are not configured at all (even if there is content for that language in the database)
- fix deleting of that language for the case where it is no longer configured in settings

```
Stacktrace (most recent call last):

  File "django/core/handlers/base.py", line 112, in get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "django/utils/decorators.py", line 99, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "django/views/decorators/cache.py", line 52, in _wrapped_view_func
    response = view_func(request, *args, **kwargs)
  File "django/contrib/admin/sites.py", line 198, in inner
    return view(request, *args, **kwargs)
  File "reversion/revisions.py", line 297, in do_revision_context
    return func(*args, **kwargs)
  File "cms/admin/pageadmin.py", line 1266, in delete_translation
    'language': force_unicode(get_language_object(language)['name'])
  File "cms/utils/i18n.py", line 115, in get_language_object
    raise LanguageError('Language not found: %s' % language_code)
```

tested with django-cms==3.0.13



Discussion:

Resolving patch:
diff --git a/cms/cms_toolbar.py b/cms/cms_toolbar.py
--- a/cms/cms_toolbar.py
+++ b/cms/cms_toolbar.py
@@ -19,7 +19,7 @@
 from cms.toolbar_base import CMSToolbar
 from cms.toolbar_pool import toolbar_pool
 from cms.utils.compat import DJANGO_1_4
-from cms.utils.i18n import get_language_tuple, force_language
+from cms.utils.i18n import get_language_tuple, force_language, get_language_dict
 from cms.utils.compat.dj import is_installed
 from cms.utils import get_cms_setting
 from cms.utils.permissions import get_user_sites_queryset, has_page_change_permission
@@ -353,13 +353,11 @@ def change_language_menu(self):
             if not language_menu:
                 return None
 
-            languages = get_language_tuple(self.current_site.pk)
-            languages_dict = dict(languages)
-
-            remove = [(code, languages_dict.get(code, code)) for code in self.page.get_languages()]
-            add = [l for l in languages if l not in remove]
-            copy = [(code, name) for code, name in languages if code != self.current_lang and (code, name) in remove]
+            languages = get_language_dict(self.current_site.pk)
 
+            remove = [(code, languages.get(code, code)) for code in self.page.get_languages() if code in languages]
+            add = [l for l in languages.items() if l not in remove]
+            copy = [(code, name) for code, name in languages.items() if code != self.current_lang and (code, name) in remove]
             if add:
                 language_menu.add_break(ADD_PAGE_LANGUAGE_BREAK)
                 page_change_url = admin_reverse('cms_page_change', args=(self.page.pk,))


Test patch:
diff --git a/cms/tests/toolbar.py b/cms/tests/toolbar.py
--- a/cms/tests/toolbar.py
+++ b/cms/tests/toolbar.py
@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 from __future__ import with_statement
 import datetime
+from operator import attrgetter
 import re
 
 from django.contrib import admin
@@ -15,7 +16,7 @@
 from django.utils.translation import ugettext_lazy as _, override
 
 from cms.api import create_page, create_title, add_plugin
-from cms.cms_toolbar import ADMIN_MENU_IDENTIFIER, ADMINISTRATION_BREAK
+from cms.cms_toolbar import ADMIN_MENU_IDENTIFIER, ADMINISTRATION_BREAK, LANGUAGE_MENU_IDENTIFIER
 from cms.middleware.toolbar import ToolbarMiddleware
 from cms.models import Page, UserSettings, PagePermission
 from cms.toolbar.items import (ToolbarAPIMixin, LinkItem, ItemSearchResult,
@@ -481,6 +482,46 @@ def test_page_edit_redirect_errors(self):
             response = self.client.post(url, {'pk': 9999, 'model': 'cms.page'})
             self.assertEqual(response.content.decode('utf-8'), '')
 
+    def test_remove_language(self):
+        item_name = attrgetter('name')
+
+        page = create_page("toolbar-page", "nav_playground.html", "en",
+                           published=True)
+        create_title(title="de page", language="de", page=page)
+        create_title(title="fr page", language="fr", page=page)
+
+        request = self.get_page_request(page, self.get_staff(), '/', edit=True)
+        toolbar = CMSToolbar(request)
+        toolbar.populate()
+        meu = toolbar.get_menu(LANGUAGE_MENU_IDENTIFIER)
+        self.assertTrue(any([item for item in meu.get_items() if hasattr(item, 'name') and item_name(item).startswith('Delete German')]))
+        self.assertTrue(any([item for item in meu.get_items() if hasattr(item, 'name') and item_name(item).startswith('Delete English')]))
+        self.assertTrue(any([item for item in meu.get_items() if hasattr(item, 'name') and item_name(item).startswith('Delete French')]))
+
+        reduced_langs = {
+            1: [
+                {
+                    'code': 'en',
+                    'name': 'English',
+                    'fallbacks': ['fr', 'de'],
+                    'public': True,
+                },
+                {
+                    'code': 'fr',
+                    'name': 'French',
+                    'public': True,
+                },
+            ]
+        }
+
+        with SettingsOverride(CMS_LANGUAGES=reduced_langs):
+            toolbar = CMSToolbar(request)
+            toolbar.populate()
+            meu = toolbar.get_menu(LANGUAGE_MENU_IDENTIFIER)
+            self.assertFalse(any([item for item in meu.get_items() if hasattr(item, 'name') and item_name(item).startswith('Delete German')]))
+            self.assertTrue(any([item for item in meu.get_items() if hasattr(item, 'name') and item_name(item).startswith('Delete English')]))
+            self.assertTrue(any([item for item in meu.get_items() if hasattr(item, 'name') and item_name(item).startswith('Delete French')]))
+
     def get_username(self, user=None, default=''):
         user = user or self.request.user
         try: