Skip to content
This repository
Browse code

Merge pull request #2 from digi604/new_i18n_settings

New i18n settings
  • Loading branch information...
commit a20471221877d808342d4234003509e29abd5dab 2 parents b6fde0a + a6f90d1
Patrick Lauber authored October 03, 2012
13  cms/admin/forms.py
@@ -3,6 +3,7 @@
3 3
 from cms.forms.widgets import UserSelectAdminWidget
4 4
 from cms.models import (Page, PagePermission, PageUser, ACCESS_PAGE, 
5 5
     PageUserGroup)
  6
+from cms.utils.i18n import get_language_tuple, get_language_list
6 7
 from cms.utils.mail import mail_page_user_change
7 8
 from cms.utils.page import is_valid_page_slug
8 9
 from cms.utils.page_resolver import get_page_from_path, is_valid_url
@@ -60,7 +61,7 @@ class PageAddForm(forms.ModelForm):
60 61
         help_text=_('The default title'))
61 62
     slug = forms.CharField(label=_("Slug"), widget=forms.TextInput(),
62 63
         help_text=_('The part of the title that is used in the URL'))
63  
-    language = forms.ChoiceField(label=_("Language"), choices=settings.CMS_LANGUAGES,
  64
+    language = forms.ChoiceField(label=_("Language"), choices=get_language_tuple(),
64 65
         help_text=_('The current language of the content fields.'))
65 66
     
66 67
     class Meta:
@@ -74,13 +75,7 @@ def __init__(self, *args, **kwargs):
74 75
         if not self.fields['site'].initial:
75 76
             self.fields['site'].initial = Site.objects.get_current().pk
76 77
         site_id = self.fields['site'].initial
77  
-        languages = []
78  
-        language_mappings = dict(settings.LANGUAGES)
79  
-        if site_id in settings.CMS_SITE_LANGUAGES:
80  
-            for lang in settings.CMS_SITE_LANGUAGES[site_id]:
81  
-                languages.append((lang, language_mappings.get(lang, lang)))
82  
-        else:
83  
-            languages = settings.CMS_LANGUAGES
  78
+        languages = get_language_tuple(site_id)
84 79
         self.fields['language'].choices = languages
85 80
         if not self.fields['language'].initial:
86 81
             self.fields['language'].initial = get_language()
@@ -142,7 +137,7 @@ def clean_slug(self):
142 137
     
143 138
     def clean_language(self):
144 139
         language = self.cleaned_data['language']
145  
-        if not language in dict(settings.CMS_LANGUAGES).keys():
  140
+        if not language in get_language_list():
146 141
             raise ValidationError("Given language does not match language settings.")
147 142
         return language
148 143
         
24  cms/admin/pageadmin.py
@@ -17,6 +17,7 @@
17 17
 from cms.utils import (copy_plugins, helpers, moderator, permissions, plugins, 
18 18
     get_template_from_request, get_language_from_request, 
19 19
     placeholder as placeholder_utils, admin as admin_utils, cms_static_url)
  20
+from cms.utils.i18n import get_language_dict, get_language_list, get_language_tuple, get_language_object
20 21
 from cms.utils.page_resolver import is_valid_url
21 22
 from cms.utils.admin import jsonify_request
22 23
 from cms.utils.permissions import has_plugin_permission
@@ -441,13 +442,13 @@ def get_form(self, request, obj=None, **kwargs):
441 442
                     installed_plugins = plugin_pool.get_all_plugins(placeholder_name, obj)
442 443
                     plugin_list = CMSPlugin.objects.filter(language=language, placeholder=placeholder, parent=None).order_by('position')
443 444
                     other_plugins = CMSPlugin.objects.filter(placeholder=placeholder, parent=None).exclude(language=language)
444  
-                    dict_cms_languages = dict(settings.CMS_LANGUAGES)
  445
+                    dict_cms_languages = get_language_dict()
445 446
                     for plugin in other_plugins:
446 447
                         if (not plugin.language in copy_languages) and (plugin.language in dict_cms_languages):
447 448
                             copy_languages[plugin.language] = dict_cms_languages[plugin.language]
448 449
 
449 450
                 language = get_language_from_request(request, obj)
450  
-                if copy_languages and len(settings.CMS_LANGUAGES) > 1:
  451
+                if copy_languages and len(get_language_list()) > 1:
451 452
                     show_copy = True
452 453
                 widget = PluginEditor(attrs={
453 454
                     'installed': installed_plugins,
@@ -565,14 +566,7 @@ def _get_site_languages(self, obj):
565 566
         site_id = None
566 567
         if obj:
567 568
             site_id = obj.site_id
568  
-        languages = []
569  
-        if site_id and site_id in settings.CMS_SITE_LANGUAGES:
570  
-            for lang in settings.CMS_SITE_LANGUAGES[site_id]:
571  
-                lang_label = dict(settings.CMS_LANGUAGES).get(lang, dict(settings.LANGUAGES).get(lang, lang))
572  
-                languages.append((lang, lang_label))
573  
-        else:
574  
-            languages = settings.CMS_LANGUAGES
575  
-        return languages
  569
+        return get_language_tuple(site_id)
576 570
 
577 571
     def update_language_tab_context(self, request, obj, context=None):
578 572
         if not context:
@@ -675,11 +669,7 @@ def changelist_view(self, request, extra_context=None):
675 669
         site_id = int(site_id)
676 670
         
677 671
         # languages
678  
-        languages = []
679  
-        if site_id and site_id in settings.CMS_SITE_LANGUAGES:
680  
-            languages = settings.CMS_SITE_LANGUAGES[site_id]
681  
-        else:
682  
-            languages = [lang[0] for lang in settings.CMS_LANGUAGES]
  672
+        languages = get_language_list(site_id)
683 673
 
684 674
         # parse the cookie that saves which page trees have
685 675
         # been opened already and extracts the page ID
@@ -985,7 +975,7 @@ def delete_translation(self, request, object_id, extra_context=None):
985 975
                 raise PermissionDenied
986 976
 
987 977
             message = _('Title and plugins with language %(language)s was deleted') % {
988  
-                'language': [name for code, name in settings.CMS_LANGUAGES if code == language][0]
  978
+                'language': get_language_object(language)['name']
989 979
             }
990 980
             self.log_change(request, titleobj, message)
991 981
             self.message_user(request, message)
@@ -1188,7 +1178,7 @@ def copy_plugins(self, request):
1188 1178
 
1189 1179
         if not page.has_change_permission(request):
1190 1180
             return HttpResponseForbidden(ugettext("You do not have permission to change this page"))
1191  
-        if not language or not language in [ lang[0] for lang in settings.CMS_LANGUAGES ]:
  1181
+        if not language or not language in get_language_list():
1192 1182
             return HttpResponseBadRequest(ugettext("Language must be set to a supported language!"))
1193 1183
         if language == copy_from:
1194 1184
             return HttpResponseBadRequest(ugettext("Language must be different than the copied language!"))
5  cms/api.py
@@ -7,6 +7,7 @@
7 7
 calling these methods!
8 8
 """
9 9
 import datetime
  10
+from cms.utils.i18n import get_language_list
10 11
 
11 12
 from django.conf import settings
12 13
 from django.contrib.auth.models import User
@@ -125,7 +126,7 @@ def create_page(title, template, language, menu_title=None, slug=None,
125 126
     assert template in [tpl[0] for tpl in settings.CMS_TEMPLATES]
126 127
     
127 128
     # validate language:
128  
-    assert language in [lang[0] for lang in settings.CMS_LANGUAGES]
  129
+    assert language in get_language_list()
129 130
     
130 131
     # set default slug:
131 132
     if not slug:
@@ -220,7 +221,7 @@ def create_title(language, title, page, menu_title=None, slug=None,
220 221
     See docs/extending_cms/api_reference.rst for more info
221 222
     """
222 223
     # validate language:
223  
-    assert language in [lang[0] for lang in settings.CMS_LANGUAGES]
  224
+    assert language in get_language_list()
224 225
     
225 226
     # validate page
226 227
     assert isinstance(page, Page)
4  cms/appresolver.py
... ...
@@ -1,7 +1,7 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 from __future__ import with_statement
3 3
 from cms.apphook_pool import apphook_pool
4  
-from cms.utils.i18n import force_language
  4
+from cms.utils.i18n import force_language, get_language_list
5 5
 from cms.utils.moderator import get_page_queryset
6 6
 
7 7
 from django.conf import settings
@@ -31,7 +31,7 @@ def applications_page_check(request, current_page=None, path=None):
31 31
         # This removes the non-CMS part of the URL.
32 32
         path = request.path.replace(reverse('pages-root'), '', 1)
33 33
     # check if application resolver can resolve this
34  
-    for lang, lang_name in settings.CMS_LANGUAGES:
  34
+    for lang in get_language_list():
35 35
         if path.startswith(lang+"/"):
36 36
             path = path[len(lang+"/"):]
37 37
     for resolver in APP_RESOLVERS:
11  cms/conf/__init__.py
... ...
@@ -1,6 +1,7 @@
1 1
 # -*- coding: utf-8 -*-
  2
+from cms.exceptions import CMSDeprecationWarning
2 3
 from django.conf import settings
3  
-from patch import post_patch, post_patch_check
  4
+from patch import post_patch, post_patch_check, pre_patch
4 5
 import warnings
5 6
 
6 7
 
@@ -9,21 +10,23 @@ def patch_settings():
9 10
     """Merge settings with global cms settings, so all required attributes
10 11
     will exist. Never override, just append non existing settings.
11 12
     
12  
-    Also check for setting inconstistence if settings.DEBUG
  13
+    Also check for setting inconsistencies if settings.DEBUG
13 14
     """
14 15
     if patch_settings.ALREADY_PATCHED:
15 16
         return
16 17
     patch_settings.ALREADY_PATCHED = True
17 18
     
18 19
     if getattr(settings, 'CMS_FLAT_URLS', False):
19  
-        warnings.warn("CMS_FLAT_URLS are deprecated and will be removed in django CMS 2.4!", DeprecationWarning)
  20
+        warnings.warn("CMS_FLAT_URLS are deprecated and will be removed in django CMS 2.4!", CMSDeprecationWarning)
20 21
     
21 22
     if getattr(settings, 'CMS_MODERATOR', False):
22  
-        warnings.warn("CMS_MODERATOR will be removed and replaced in django CMS 2.4!", DeprecationWarning)
  23
+        warnings.warn("CMS_MODERATOR will be removed and replaced in django CMS 2.4!", CMSDeprecationWarning)
23 24
     
24 25
     from cms.conf import global_settings
25 26
     # patch settings
26 27
 
  28
+    pre_patch()
  29
+
27 30
     # merge with global cms settings
28 31
     for attr in dir(global_settings):
29 32
         if attr == attr.upper() and not hasattr(settings, attr):
53  cms/conf/global_settings.py
@@ -70,34 +70,45 @@
70 70
 # Wheter the cms has a softroot functionionality
71 71
 CMS_SOFTROOT = False
72 72
 
73  
-#Hide untranslated Pages
74  
-CMS_HIDE_UNTRANSLATED = True
75 73
 
76  
-#Fall back to another language if the requested page isn't available in the preferred language
77  
-CMS_LANGUAGE_FALLBACK = True
78 74
 
79  
-#Configuration on how to order the fallbacks for languages.
80  
-# example: {'de': ['en', 'fr'],
81  
-#           'en': ['de'],
82  
-#          }
83  
-CMS_LANGUAGE_CONF = {}
  75
+# Defines which languages should be offered and what are the defaults
  76
+# example:
  77
+# CMS_LANGUAGES = {
  78
+#    1: [
  79
+#        {
  80
+#            'code': 'en',
  81
+#            'name': _('English'),
  82
+#            'fallbacks': ['de', 'fr'],
  83
+#            'public': True,
  84
+#            'hide_untranslated': True,
  85
+#            'redirect_on_fallback':False,
  86
+#            },
  87
+#        {
  88
+#            'code': 'de',
  89
+#            'name': _('Deutsch'),
  90
+#            'fallbacks': ['en', 'fr'],
  91
+#            'public': True,
  92
+#            },
  93
+#        {
  94
+#            'code': 'fr',
  95
+#            'public': False,
  96
+#            }
  97
+#    ],
  98
+#    'default': {
  99
+#        'fallbacks': ['en', 'de', 'fr'],
  100
+#        'redirect_on_fallback':True,
  101
+#        'public': False,
  102
+#        'hide_untranslated': False,
  103
+#        }
  104
+#}
  105
+
  106
+#CMS_LANGUAGES = {}
84 107
 
85  
-# Defines which languages should be offered.
86  
-CMS_LANGUAGES = settings.LANGUAGES
87  
-
88  
-# If you have different sites with different languages you can configure them here
89  
-# and you will only be able to edit the languages that are actually on the site.
90  
-# example: {1:['en','de'],
91  
-#           2:['en','fr'],
92  
-#           3:['en'],}
93  
-CMS_SITE_LANGUAGES = {}
94 108
 
95 109
 CMS_SITE_CHOICES_CACHE_KEY = 'CMS:site_choices'
96 110
 CMS_PAGE_CHOICES_CACHE_KEY = 'CMS:page_choices'
97 111
 
98  
-# Languages that are visible in the frontend (Language Chooser)
99  
-CMS_FRONTEND_LANGUAGES = [x[0] for x in CMS_LANGUAGES]
100  
-
101 112
 
102 113
 # Path for CMS media (uses <MEDIA_ROOT>/cms by default)
103 114
 CMS_MEDIA_PATH = 'cms/'
123  cms/conf/patch.py
... ...
@@ -1,12 +1,21 @@
1 1
 # -*- coding: utf-8 -*-
  2
+from cms.exceptions import CMSDeprecationWarning
2 3
 from django.conf import settings
3 4
 from django.core.exceptions import ImproperlyConfigured
4 5
 from django.utils.translation import ugettext_lazy  as _
5 6
 from sekizai.helpers import validate_template
  7
+import warnings
6 8
 
  9
+def pre_patch():
  10
+    """Patch settings for dynamic defaults"""
  11
+    if not getattr(settings, 'CMS_LANGUAGES', False):
  12
+        settings.CMS_LANGUAGES = {settings.SITE_ID:[]}
  13
+        for code, name in settings.LANGUAGES:
  14
+            lang = {'code':code, 'name':_(name)}
  15
+            settings.CMS_LANGUAGES[settings.SITE_ID].append(lang)
7 16
 
8 17
 def post_patch():
9  
-    """Patch settings after global are adde
  18
+    """Patch settings after global are added
10 19
     """
11 20
     if settings.CMS_TEMPLATE_INHERITANCE:
12 21
         # Append the magic inheritance template
@@ -46,3 +55,115 @@ def post_patch_check():
46 55
                 "I can't find the namespaces in %r."
47 56
                 % template[0]
48 57
             )
  58
+    VALID_LANG_PROPS = ['code', 'name', 'fallbacks', 'hide_untranslated', 'redirect_on_fallback', 'public']
  59
+    try:
  60
+        for site in settings.CMS_LANGUAGES:
  61
+            try:
  62
+                int(site)
  63
+            except ValueError:
  64
+                if not site =="default":
  65
+                    raise ImproperlyConfigured("CMS_LANGUAGES can only be filled with integers (site ids) and 'default'"
  66
+                                               " for default values. %s is not a valid key." % site)
  67
+            for language in settings.CMS_LANGUAGES[site]:
  68
+                if site == "default":
  69
+                    if language not in VALID_LANG_PROPS:
  70
+                        raise ImproperlyConfigured("CMS_LANGUAGES has an invalid property in the default properties: %(property)s" %
  71
+                                                   {'property':language})
  72
+                    continue
  73
+                if not "code" in language.keys():
  74
+                    raise ImproperlyConfigured("CMS_LANGUAGES has a language without a 'code' property")
  75
+                if not 'name' in language.keys():
  76
+                    raise ImproperlyConfigured("CMS_LANGUAGES has a language without a 'name' property")
  77
+                for key in language:
  78
+                    if key not in VALID_LANG_PROPS:
  79
+                        raise ImproperlyConfigured("CMS_LANGUAGES has an invalid property on the site %(site)s and "
  80
+                                                   "language %(language)s: %(property)s" %
  81
+                                                   {'site':site, 'language':language['code'], 'property':key})
  82
+                # Fill up the defaults
  83
+                if not language.has_key('fallbacks'):
  84
+                    fallbacks = []
  85
+                    for tmp_language in settings.CMS_LANGUAGES[site]:
  86
+                        tmp_language = tmp_language.copy()
  87
+                        if not tmp_language.has_key('public'):
  88
+                            if settings.CMS_LANGUAGES.has_key('default'):
  89
+                                tmp_language['public'] = settings.CMS_LANGUAGES['default'].get('public', True)
  90
+                            else:
  91
+                                tmp_language['public'] = True
  92
+                        if tmp_language['public']:
  93
+                            fallbacks.append(tmp_language['code'])
  94
+                    if fallbacks:
  95
+                        fallbacks.remove(language['code'])
  96
+                    if settings.CMS_LANGUAGES.has_key('default'):
  97
+                        language['fallbacks'] = settings.CMS_LANGUAGES['default'].get('fallbacks', fallbacks)
  98
+                    else:
  99
+                        language['fallbacks'] = fallbacks
  100
+                if not language.has_key('public'):
  101
+                    if settings.CMS_LANGUAGES.has_key('default'):
  102
+                        language['public'] = settings.CMS_LANGUAGES['default'].get('public', True)
  103
+                    else:
  104
+                        language['public'] = True
  105
+                if not language.has_key('redirect_on_fallback'):
  106
+                    if settings.CMS_LANGUAGES.has_key('default'):
  107
+                        language['redirect_on_fallback'] = settings.CMS_LANGUAGES['default'].get('redirect_on_fallback', True)
  108
+                    else:
  109
+                        language['redirect_on_fallback'] = True
  110
+                if not language.has_key('hide_untranslated'):
  111
+                    if settings.CMS_LANGUAGES.has_key('default'):
  112
+                        language['hide_untranslated'] = settings.CMS_LANGUAGES['default'].get('hide_untranslated', True)
  113
+                    else:
  114
+                        language['hide_untranslated'] = True
  115
+    except TypeError:
  116
+        if type(settings.CMS_LANGUAGES) == tuple:
  117
+            new_languages = {}
  118
+            lang_template = {'code':'', 'name':'', 'fallbacks':[],'public':True, 'redirect_on_fallback':True, 'hide_untranslated':False}
  119
+            if hasattr(settings,'CMS_HIDE_UNTRANSLATED'):
  120
+                lang_template['hide_untranslated'] = settings.CMS_HIDE_UNTRANSLATED
  121
+            if hasattr(settings, 'CMS_SITE_LANGUAGES'):
  122
+                for site in settings.CMS_SITE_LANGUAGES:
  123
+                    new_languages[site] = []
  124
+            else:
  125
+                new_languages[1]=[]
  126
+
  127
+            if hasattr(settings, 'CMS_SITE_LANGUAGES'):
  128
+                for site in settings.CMS_SITE_LANGUAGES:
  129
+                    for site_code in settings.CMS_SITE_LANGUAGES[site]:
  130
+                        for code, name in settings.CMS_LANGUAGES:
  131
+                            if code == site_code:
  132
+                                new_languages[site].append(get_old_language_conf(code, name, lang_template))
  133
+            else:
  134
+                for code, name in settings.CMS_LANGUAGES:
  135
+                    new_languages[1].append(get_old_language_conf(code, name, lang_template))
  136
+            settings.CMS_LANGUAGES = new_languages
  137
+            import pprint
  138
+            pp = pprint.PrettyPrinter(indent=4)
  139
+            warnings.warn(
  140
+                "CMS_LANGUAGES has changed in django-cms 2.4\nYou may replace CMS_LANGUAGES with the following:\n%s" % pp.pformat(settings.CMS_LANGUAGES),
  141
+                CMSDeprecationWarning)
  142
+
  143
+        else:
  144
+            raise ImproperlyConfigured("CMS_LANGUAGES has changed and has some errors. Please refer to the docs.")
  145
+
  146
+
  147
+def get_old_language_conf(code, name, template):
  148
+    language = template.copy()
  149
+    language['code'] = code
  150
+    language['name'] = name
  151
+    default_fallbacks = dict(settings.CMS_LANGUAGES).keys()
  152
+    if hasattr(settings, 'CMS_LANGUAGE_FALLBACK'):
  153
+        if settings.CMS_LANGUAGE_FALLBACK:
  154
+            if hasattr(settings, 'CMS_LANGUAGE_CONF'):
  155
+                language['fallbacks'] = settings.CMS_LANGUAGE_CONF.get(code, default_fallbacks)
  156
+            else:
  157
+                language['fallbacks'] = default_fallbacks
  158
+        else:
  159
+            language['fallbacks'] = []
  160
+    else:
  161
+        if hasattr(settings, 'CMS_LANGUAGE_CONF'):
  162
+            language['fallbacks'] = settings.CMS_LANGUAGE_CONF.get(code, default_fallbacks)
  163
+        else:
  164
+            language['fallbacks'] = default_fallbacks
  165
+    if hasattr(settings, 'CMS_FRONTEND_LANGUAGES'):
  166
+        language['public'] = code in settings.CMS_FRONTEND_LANGUAGES
  167
+    return language
  168
+
  169
+
4  cms/exceptions.py
@@ -35,4 +35,6 @@ class Deprecated(Exception): pass
35 35
     
36 36
 class DuplicatePlaceholderWarning(Warning): pass
37 37
 
38  
-class DontUsePageAttributeWarning(Warning): pass
  38
+class DontUsePageAttributeWarning(Warning): pass
  39
+
  40
+class CMSDeprecationWarning(Warning):pass
6  cms/menu.py
@@ -6,7 +6,7 @@
6 6
 from cms.models.permissionmodels import PagePermission, GlobalPagePermission
7 7
 from cms.models.titlemodels import Title
8 8
 from cms.utils import get_language_from_request
9  
-from cms.utils.i18n import get_fallback_languages
  9
+from cms.utils.i18n import get_fallback_languages, hide_untranslated
10 10
 from cms.utils.moderator import get_page_queryset, get_title_queryset
11 11
 from cms.utils.plugins import current_site
12 12
 from menus.base import Menu, NavigationNode, Modifier
@@ -229,7 +229,7 @@ def get_nodes(self, request):
229 229
             'site':site,
230 230
         }
231 231
         
232  
-        if settings.CMS_HIDE_UNTRANSLATED:
  232
+        if hide_untranslated(lang, site.pk):
233 233
             filters['title_set__language'] = lang
234 234
             
235 235
         pages = page_queryset.published().filter(**filters).order_by("tree_id", "lft")
@@ -272,7 +272,7 @@ def get_nodes(self, request):
272 272
                     nodes.append(page_to_node(page, home, home_cut))
273 273
                     ids.remove(page.pk)
274 274
 
275  
-        if ids: # get fallback languages
  275
+        if ids and not hide_untranslated(lang): # get fallback languages if allowed
276 276
             fallbacks = get_fallback_languages(lang)
277 277
             for lang in fallbacks:
278 278
                 titles = list(get_title_queryset(request).filter(page__in=ids, language=lang))
1  cms/models/pagemodel.py
@@ -530,6 +530,7 @@ def get_languages(self):
530 530
             self.all_languages = Title.objects.filter(page=self).values_list("language", flat=True).distinct()
531 531
             self.all_languages = list(self.all_languages)
532 532
             self.all_languages.sort()
  533
+            self.all_languages = map(str, self.all_languages)
533 534
         return self.all_languages
534 535
 
535 536
     def get_cached_ancestors(self, ascending=True):
2  cms/plugins/inherit/cms_plugins.py
@@ -36,7 +36,7 @@ def render(self, context, instance, placeholder):
36 36
             page = instance.from_page
37 37
         else:
38 38
             page = instance.page
39  
-        if not instance.page.publisher_is_draft and page.publisher_is_draft:
  39
+        if settings.CMS_MODERATOR and not instance.page.publisher_is_draft and page.publisher_is_draft:
40 40
             page = page.publisher_public
41 41
             
42 42
         plugins = get_cmsplugin_queryset(request).filter(
4  cms/plugins/inherit/models.py
... ...
@@ -1,11 +1,11 @@
  1
+from cms.utils.i18n import get_language_tuple
1 2
 from django.db import models
2 3
 from django.utils.translation import ugettext_lazy as _
3 4
 from cms.models import CMSPlugin, Page
4  
-from django.conf import settings
5 5
 
6 6
 class InheritPagePlaceholder(CMSPlugin):
7 7
     """
8 8
     Provides the ability to inherit plugins for a certain placeholder from an associated "parent" page instance
9 9
     """
10 10
     from_page = models.ForeignKey(Page, null=True, blank=True, help_text=_("Choose a page to include its plugins into this placeholder, empty will choose current page"))
11  
-    from_language = models.CharField(_("language"), max_length=5, choices=settings.CMS_LANGUAGES, blank=True, null=True, help_text=_("Optional: the language of the plugins you want"))
  11
+    from_language = models.CharField(_("language"), max_length=5, choices=get_language_tuple(), blank=True, null=True, help_text=_("Optional: the language of the plugins you want"))
13  cms/plugins/utils.py
@@ -4,6 +4,7 @@
4 4
 
5 5
 from cms.plugin_pool import plugin_pool
6 6
 from cms.utils import get_language_from_request
  7
+from cms.utils.i18n import get_redirect_on_fallback, get_fallback_languages
7 8
 from cms.utils.moderator import get_cmsplugin_queryset
8 9
 
9 10
 def get_plugins(request, placeholder, lang=None):
@@ -24,9 +25,17 @@ def assign_plugins(request, placeholders, lang=None):
24 25
     if not placeholders:
25 26
         return
26 27
     lang = lang or get_language_from_request(request)
27  
-
  28
+    request_lang = lang
  29
+    if hasattr(request, "current_page") and request.current_page is not None:
  30
+        languages = request.current_page.get_languages()
  31
+        if not lang in languages and not get_redirect_on_fallback(lang):
  32
+            fallbacks = get_fallback_languages(lang)
  33
+            for fallback in fallbacks:
  34
+                if fallback in languages:
  35
+                    request_lang = fallback
  36
+                    break
28 37
     # get all plugins for the given placeholders
29  
-    qs = get_cmsplugin_queryset(request).filter(placeholder__in=placeholders, language=lang, parent__isnull=True).order_by('placeholder', 'position')
  38
+    qs = get_cmsplugin_queryset(request).filter(placeholder__in=placeholders, language=request_lang, parent__isnull=True).order_by('placeholder', 'position')
30 39
     plugin_list = downcast_plugins(qs)
31 40
 
32 41
     # split the plugins up by placeholder
3  cms/templatetags/cms_admin.py
@@ -4,6 +4,7 @@
4 4
 from classytags.helpers import InclusionTag
5 5
 from cms.models import MASK_PAGE, MASK_CHILDREN, MASK_DESCENDANTS
6 6
 from cms.utils.admin import get_admin_menu_item_context
  7
+from cms.utils.i18n import get_language_object
7 8
 from cms.utils.permissions import get_any_page_view_permissions
8 9
 from distutils.version import LooseVersion
9 10
 from django import template
@@ -209,7 +210,7 @@ def get_context(self, context):
209 210
             'is_popup': is_popup,
210 211
             'show_save': True,
211 212
             'language': language,
212  
-            'language_name': [name for langcode, name in settings.CMS_LANGUAGES if langcode == language][0],
  213
+            'language_name': get_language_object(language)['name'],
213 214
             'show_delete_translation': show_delete_translation
214 215
         }
215 216
 register.tag(PageSubmitRow)
65  cms/test_utils/cli.py
@@ -104,21 +104,55 @@ def configure(**extra):
104 104
             ('pt-br', gettext('Brazilian Portuguese')),
105 105
             ('nl', gettext("Dutch")),
106 106
         ),
107  
-        CMS_LANGUAGES = (
108  
-            ('en', gettext('English')),
109  
-            ('fr', gettext('French')),
110  
-            ('de', gettext('German')),
111  
-            ('pt-br', gettext('Brazilian Portuguese')),
112  
-            ('nl', gettext("Dutch")),
113  
-        ),
114  
-        CMS_LANGUAGE_CONF = {
115  
-            'de':['fr', 'en'],
116  
-            'en':['fr', 'de'],
117  
-        },
118  
-        CMS_SITE_LANGUAGES = {
119  
-            1:['en','de','fr','pt-br'],
120  
-            2:['de','fr'],
121  
-            3:['nl'],
  107
+        CMS_LANGUAGES = {
  108
+            1: [
  109
+                {
  110
+                    'code':'en',
  111
+                    'name':gettext('English'),
  112
+                    'fallbacks':['fr','de'],
  113
+                    'public':True,
  114
+                },
  115
+                {
  116
+                    'code':'de',
  117
+                    'name':gettext('German'),
  118
+                    'fallbacks':['fr','en'],
  119
+                    'public':True,
  120
+                },
  121
+                {
  122
+                    'code':'fr',
  123
+                    'name':gettext('French'),
  124
+                    'public':True,
  125
+                },
  126
+                {
  127
+                    'code':'pt-br',
  128
+                    'name':gettext('Brazilian Portuguese'),
  129
+                    'public':True,
  130
+                },
  131
+            ],
  132
+            2: [
  133
+                {
  134
+                    'code':'de',
  135
+                    'name':gettext('German'),
  136
+                    'fallbacks':['fr','en'],
  137
+                    'public':True,
  138
+                },
  139
+                {
  140
+                    'code':'fr',
  141
+                    'name':gettext('French'),
  142
+                    'public':True,
  143
+                },
  144
+            ],
  145
+            3: [
  146
+                {
  147
+                    'code':'nl',
  148
+                    'name':gettext('Dutch'),
  149
+                    'fallbacks':['fr','en'],
  150
+                    'public':True,
  151
+                },
  152
+            ],
  153
+            'default': {
  154
+                'hide_untranslated':False,
  155
+            },
122 156
         },
123 157
         CMS_TEMPLATES = (
124 158
             ('col_two.html', gettext('two columns')),
@@ -163,7 +197,6 @@ def configure(**extra):
163 197
         CMS_SEO_FIELDS = True,
164 198
         CMS_FLAT_URLS = False,
165 199
         CMS_MENU_TITLE_OVERWRITE = True,
166  
-        CMS_HIDE_UNTRANSLATED = False,
167 200
         CMS_URL_OVERWRITE = True,
168 201
         CMS_SHOW_END_DATE = True,
169 202
         CMS_SHOW_START_DATE = True,
2  cms/test_utils/testcases.py
@@ -9,6 +9,7 @@
9 9
 from django.template.context import Context
10 10
 from django.test import testcases
11 11
 from django.test.client import Client, RequestFactory
  12
+from django.utils.translation import activate
12 13
 from menus.menu_pool import menu_pool
13 14
 from urlparse import urljoin
14 15
 import sys
@@ -72,6 +73,7 @@ def _fixture_setup(self):
72 73
         super(CMSTestCase, self)._fixture_setup()
73 74
         self.create_fixtures()
74 75
         self.client = Client()
  76
+        activate("en")
75 77
 
76 78
     def create_fixtures(self):
77 79
         pass
8  cms/tests/apphooks.py
@@ -355,13 +355,15 @@ def test_page_language_url_for_apphook(self):
355 355
             url = output['content']
356 356
             self.assertEqual(url, '/en/child_page/child_child_page/extra_1/')
357 357
 
358  
-            output = tag.get_context(fake_context, 'ja')
359  
-            url = output['content']
360  
-            self.assertEqual(url, '/ja/child_page/child_child_page/extra_1/')
  358
+
361 359
 
362 360
             output = tag.get_context(fake_context, 'de')
363 361
             url = output['content']
364 362
             # look the extra "_de"
365 363
             self.assertEqual(url, '/de/child_page/child_child_page_de/extra_1/')
366 364
 
  365
+            output = tag.get_context(fake_context, 'fr')
  366
+            url = output['content']
  367
+            self.assertEqual(url, '/fr/child_page/child_child_page/extra_1/')
  368
+
367 369
             apphook_pool.clear()
7  cms/tests/menu.py
... ...
@@ -1,5 +1,6 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 from __future__ import with_statement
  3
+import copy
3 4
 from cms.api import create_page
4 5
 from cms.menu import CMSMenu, get_visible_pages
5 6
 from cms.models import Page
@@ -268,11 +269,13 @@ def test_show_breadcrumb(self):
268 269
         
269 270
     def test_language_chooser(self):
270 271
         # test simple language chooser with default args
271  
-        with SettingsOverride(CMS_FRONTEND_LANGUAGES = ('fr','de','nl')):
  272
+        lang_settings = copy.deepcopy(settings.CMS_LANGUAGES)
  273
+        lang_settings[1][0]['public'] = False
  274
+        with SettingsOverride(CMS_LANGUAGES=lang_settings):
272 275
             context = self.get_context(path=self.get_page(3).get_absolute_url())
273 276
             tpl = Template("{% load menu_tags %}{% language_chooser %}")
274 277
             tpl.render(context)
275  
-            self.assertEqual(len(context['languages']), 2)
  278
+            self.assertEqual(len(context['languages']), 3)
276 279
             # try a different template and some different args
277 280
             tpl = Template("{% load menu_tags %}{% language_chooser 'menu/test_language_chooser.html' %}")
278 281
             tpl.render(context)
54  cms/tests/multilingual.py
... ...
@@ -1,9 +1,12 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 from __future__ import with_statement
  3
+import copy
3 4
 from cms.api import create_page, create_title, publish_page, add_plugin
4 5
 from cms.test_utils.testcases import SettingsOverrideTestCase
5 6
 from cms.test_utils.util.context_managers import SettingsOverride
6 7
 from cms.test_utils.util.mock import AttributeObject
  8
+from django.conf import settings
  9
+
7 10
 from django.contrib.auth.models import User
8 11
 from django.http import Http404, HttpResponseRedirect
9 12
 
@@ -36,7 +39,9 @@ def test_multilingual_page(self):
36 39
         self.assertEqual(placeholder.cmsplugin_set.filter(language='en').count(), 1)
37 40
 
38 41
     def test_frontend_lang(self):
39  
-        with SettingsOverride(CMS_FRONTEND_LANGUAGES=('fr', 'de', 'nl')):
  42
+        lang_settings = copy.deepcopy(settings.CMS_LANGUAGES)
  43
+        lang_settings[1][0]['public'] = False
  44
+        with SettingsOverride(CMS_LANGUAGES=lang_settings, LANGUAGE_CODE="en"):
40 45
             page = create_page("page1", "nav_playground.html", "en")
41 46
             create_title("de", page.get_title(), page, slug=page.get_slug())
42 47
             page2 = create_page("page2", "nav_playground.html", "en")
@@ -61,12 +66,12 @@ def test_detail_view_404_when_no_language_is_found(self):
61 66
         page.publish()
62 67
 
63 68
         with SettingsOverride(TEMPLATE_CONTEXT_PROCESSORS=[],
64  
-            CMS_LANGUAGES=[
65  
-                ('x-klingon', 'Klingon'),
66  
-                ('x-elvish', 'Elvish')
67  
-            ], CMS_FRONTEND_LANGUAGES=('x-klingon', 'x-elvish')):
  69
+            CMS_LANGUAGES={
  70
+                1:[
  71
+                    {'code':'x-klingon', 'name':'Klingon','public':True, 'fallbacks':[]},
  72
+                    {'code':'x-elvish', 'name':'Elvish', 'public':True, 'fallbacks':[]},
  73
+               ]}):
68 74
             from cms.views import details
69  
-
70 75
             request = AttributeObject(
71 76
                 REQUEST={'language': 'x-elvish'},
72 77
                 GET=[],
@@ -86,14 +91,12 @@ def test_detail_view_fallback_language(self):
86 91
         '''
87 92
         page = create_page("page1", "nav_playground.html", "en")
88 93
         with SettingsOverride(TEMPLATE_CONTEXT_PROCESSORS=[],
89  
-            CMS_LANGUAGE_CONF={
90  
-                'x-elvish': ['x-klingon', 'en', ]
91  
-            },
92  
-            CMS_LANGUAGES=[
93  
-                ('x-klingon', 'Klingon'),
94  
-                ('x-elvish', 'Elvish'),
95  
-            ],
96  
-            CMS_FRONTEND_LANGUAGES=('x-klingon', 'x-elvish')):
  94
+            CMS_LANGUAGES={
  95
+                1:[
  96
+                    {'code':'x-klingon', 'name':'Klingon', 'public':True, 'fallbacks':[]},
  97
+                    {'code':'x-elvish', 'name':'Elvish', 'public':True, 'fallbacks':['x-klingon', 'en', ]},
  98
+                    ]},
  99
+            ):
97 100
             create_title("x-klingon", "futla ak", page, slug=page.get_slug())
98 101
             page.publish()
99 102
             from cms.views import details
@@ -112,4 +115,25 @@ def test_detail_view_fallback_language(self):
112 115
             response = details(request, '')
113 116
             self.assertTrue(isinstance(response, HttpResponseRedirect))
114 117
 
115  
-
  118
+    def test_language_fallback(self):
  119
+        """
  120
+        Test language fallbacks in details view
  121
+        """
  122
+        from cms.views import details
  123
+        p1 = create_page("page", "nav_playground.html", "en", published=True)
  124
+        request = self.get_request('/de/', 'de')
  125
+        response = details(request, p1.get_path())
  126
+        self.assertEqual(response.status_code, 302)
  127
+        self.assertEqual(response['Location'], '/en/')
  128
+        lang_settings = copy.deepcopy(settings.CMS_LANGUAGES)
  129
+        lang_settings[1][0]['fallbacks'] = []
  130
+        lang_settings[1][1]['fallbacks'] = []
  131
+        with SettingsOverride(CMS_LANGUAGES=lang_settings):
  132
+            response = self.client.get("/de/")
  133
+            self.assertEquals(response.status_code, 404)
  134
+        lang_settings = copy.deepcopy(settings.CMS_LANGUAGES)
  135
+        lang_settings[1][0]['redirect_on_fallback'] = False
  136
+        lang_settings[1][1]['redirect_on_fallback'] = False
  137
+        with SettingsOverride(CMS_LANGUAGES=lang_settings):
  138
+            response = self.client.get("/de/")
  139
+            self.assertEquals(response.status_code, 200)
6  cms/tests/page.py
@@ -397,7 +397,7 @@ def test_edit_page_other_site_and_language(self):
397 397
         page_data = self.get_new_page_data()
398 398
         page_data['site'] = site.pk
399 399
         page_data['title'] = 'changed title'
400  
-        TESTLANG = settings.CMS_SITE_LANGUAGES[site.pk][0]
  400
+        TESTLANG = settings.CMS_LANGUAGES[site.pk][0]['code']
401 401
         page_data['language'] = TESTLANG
402 402
         superuser = self.get_superuser()
403 403
         with self.login_user_context(superuser):
@@ -744,8 +744,10 @@ def test_plugin_loading_queries(self):
744 744
 
745 745
             # trigger the apphook query so that it doesn't get in our way
746 746
             reverse('pages-root')
  747
+            # trigger the get_languages query so it doesn't get in our way
  748
+            context = self.get_context()
  749
+            context['request'].current_page.get_languages()
747 750
             with self.assertNumQueries(4):
748  
-                context = self.get_context()
749 751
                 for i, placeholder in enumerate(placeholders):
750 752
                     content = get_placeholder_content(context, context['request'], page, placeholder.slot, False)
751 753
                     for j in range(5):
6  cms/tests/site.py
@@ -33,14 +33,14 @@ def tearDown(self):
33 33
     def test_site_framework(self):
34 34
         #Test the site framework, and test if it's possible to disable it
35 35
         with SettingsOverride(SITE_ID=self.site2.pk):
36  
-            create_page("page_2a", "nav_playground.html", "en", site=self.site2)
  36
+            create_page("page_2a", "nav_playground.html", "de", site=self.site2)
37 37
     
38 38
             response = self.client.get("/en/admin/cms/page/?site__exact=%s" % self.site3.pk)
39 39
             self.assertEqual(response.status_code, 200)
40  
-            create_page("page_3b", "nav_playground.html", "en", site=self.site3)
  40
+            create_page("page_3b", "nav_playground.html", "de", site=self.site3)
41 41
             
42 42
         with SettingsOverride(SITE_ID=self.site3.pk):
43  
-            create_page("page_3a", "nav_playground.html", "en", site=self.site3)
  43
+            create_page("page_3a", "nav_playground.html", "nl", site=self.site3)
44 44
             
45 45
             # with param
46 46
             self.assertEqual(Page.objects.on_site(self.site2.pk).count(), 1)
10  cms/tests/templatetags.py
... ...
@@ -1,4 +1,5 @@
1 1
 from __future__ import with_statement
  2
+import copy
2 3
 from cms.api import create_page, create_title
3 4
 from cms.models.pagemodel import Page, Placeholder
4 5
 from cms.templatetags.cms_tags import (get_site_id, _get_page_by_untyped_arg,
@@ -142,8 +143,9 @@ def test_untranslated_language_url(self):
142 143
         page_3 = create_page('Page 3', 'nav_playground.html', 'en',  page_2, published=True,
143 144
                              in_navigation=True, reverse_id='page3')
144 145
         tpl = Template("{% load menu_tags %}{% page_language_url 'de' %}")
145  
-
146  
-        with SettingsOverride(CMS_HIDE_UNTRANSLATED=False):
  146
+        lang_settings = copy.deepcopy(settings.CMS_LANGUAGES)
  147
+        lang_settings[1][1]['hide_untranslated'] = False
  148
+        with SettingsOverride(CMS_LANGUAGES=lang_settings):
147 149
             context = self.get_context(page_2.get_absolute_url())
148 150
             context['request'].current_page = page_2
149 151
             res = tpl.render(context)
@@ -153,8 +155,8 @@ def test_untranslated_language_url(self):
153 155
             context['request'].current_page = page_3
154 156
             res = tpl.render(context)
155 157
             self.assertEqual(res,"/de/page-3/")
156  
-
157  
-        with SettingsOverride(CMS_HIDE_UNTRANSLATED=True):
  158
+        lang_settings[1][1]['hide_untranslated'] = True
  159
+        with SettingsOverride(CMS_LANGUAGES=lang_settings):
158 160
             context = self.get_context(page_2.get_absolute_url())
159 161
             context['request'].current_page = page_2
160 162
             res = tpl.render(context)
12  cms/tests/views.py
@@ -34,17 +34,7 @@ def test_handle_no_page(self):
34 34
             response = _handle_no_page(request, slug)
35 35
             self.assertEqual(response.status_code, 200)
36 36
             
37  
-    def test_language_fallback(self):
38  
-        """
39  
-        Test language fallbacks in details view
40  
-        """
41  
-        p1 = create_page("page", "nav_playground.html", "en", published=True)
42  
-        request = self.get_request('/de/', 'de')
43  
-        response = details(request, p1.get_path())
44  
-        self.assertEqual(response.status_code, 302)
45  
-        self.assertEqual(response['Location'], '/en/')
46  
-        with SettingsOverride(CMS_LANGUAGE_FALLBACK=False):
47  
-            self.assertRaises(Http404, details, request, '')
  37
+
48 38
     
49 39
     def test_apphook_not_hooked(self):
50 40
         """
6  cms/utils/__init__.py
... ...
@@ -1,6 +1,6 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 # TODO: this is just stuff from utils.py - should be splitted / moved
3  
-from cms.utils.i18n import get_default_language
  3
+from cms.utils.i18n import get_default_language, get_language_list
4 4
 from distutils.version import LooseVersion
5 5
 from django.conf import settings
6 6
 from django.core.files.storage import get_storage_class
@@ -46,12 +46,12 @@ def get_language_from_request(request, current_page=None):
46 46
     """
47 47
     language = request.REQUEST.get('language', None)
48 48
     if language:
49  
-        if not language in dict(settings.CMS_LANGUAGES).keys():
  49
+        if not language in get_language_list():
50 50
             language = None
51 51
     if language is None:
52 52
         language = getattr(request, 'LANGUAGE_CODE', None)
53 53
     if language:
54  
-        if not language in dict(settings.CMS_LANGUAGES).keys():
  54
+        if not language in get_language_list():
55 55
             language = None
56 56
 
57 57
     if language is None and current_page:
9  cms/utils/admin.py
@@ -7,7 +7,7 @@
7 7
 from django.contrib.sites.models import Site
8 8
 
9 9
 from cms.models import Page
10  
-from cms.utils import permissions, moderator, get_language_from_request
  10
+from cms.utils import permissions, moderator, get_language_from_request, get_language_list
11 11
 from cms.utils.permissions import has_global_page_permission
12 12
 
13 13
 NOT_FOUND_RESPONSE = "NotFound"
@@ -91,12 +91,7 @@ def render_admin_menu_item(request, page, template=None):
91 91
         return HttpResponse(NOT_FOUND_RESPONSE) # Not found - tree will remove item
92 92
         
93 93
     # languages
94  
-    languages = []
95  
-    if page.site_id in settings.CMS_SITE_LANGUAGES:
96  
-        languages = settings.CMS_SITE_LANGUAGES[page.site_id]
97  
-    else:
98  
-        languages = [x[0] for x in settings.CMS_LANGUAGES]
99  
-    
  94
+    languages = get_language_list(page.site_id)
100 95
     context = RequestContext(request, {
101 96
         'has_add_permission': permissions.has_page_add_permission(request),
102 97
         'site_languages': languages,
155  cms/utils/i18n.py
@@ -2,53 +2,150 @@
2 2
 from django.conf import settings
3 3
 from django.utils import translation
4 4
 
  5
+class force_language(object):
  6
+    def __init__(self, new_lang):
  7
+        self.new_lang = new_lang
  8
+        self.old_lang = translation.get_language()
  9
+
  10
+    def __enter__(self):
  11
+        translation.activate(self.new_lang)
  12
+
  13
+    def __exit__(self, type, value, tb):
  14
+        translation.activate(self.old_lang)
  15
+
  16
+
  17
+def get_language_list(site_id=None):
  18
+    """
  19
+    :return: returns a list of iso2codes for this site
  20
+    """
  21
+    site_id = get_site(site_id)
  22
+    languages = []
  23
+    for language in settings.CMS_LANGUAGES[site_id]:
  24
+        languages.append(language['code'])
  25
+    return languages
  26
+
  27
+
  28
+def get_language_tuple(site_id=None):
  29
+    """
  30
+    :return: returns an list of tuples like the old CMS_LANGUAGES or the LANGUAGES for this site
  31
+    """
  32
+    site_id = get_site(site_id)
  33
+    languages = []
  34
+    for language in settings.CMS_LANGUAGES[site_id]:
  35
+        languages.append((language['code'], language['name']))
  36
+    return languages
  37
+
  38
+
  39
+def get_language_dict(site_id=None):
  40
+    """
  41
+    :return: returns an dict of cms languages
  42
+    """
  43
+    site_id = get_site(site_id)
  44
+    languages = {}
  45
+    for language in settings.CMS_LANGUAGES[site_id]:
  46
+        languages[language['code']] = language['name']
  47
+    return languages
  48
+
  49
+
  50
+def get_public_languages(site_id=None):
  51
+    """
  52
+    :return: list of iso2codes of public languages for this site
  53
+    """
  54
+    languages = []
  55
+    for language in get_language_objects(site_id):
  56
+        if language["public"]:
  57
+            languages.append(language['code'])
  58
+    return languages
  59
+
  60
+
  61
+def get_language_object(language_code, site_id=None):
  62
+    """
  63
+    :param language_code: RFC5646 language code
  64
+    :return: the language object filled up by defaults
  65
+    """
  66
+    site_id = get_site(site_id)
  67
+    for language in settings.CMS_LANGUAGES[site_id]:
  68
+        if language['code'] == language_code:
  69
+            return language
  70
+    raise LanguageError('Language not found: %s' % language_code)
  71
+
  72
+
  73
+def get_language_objects(site_id=None):
  74
+    """
  75
+    returns list of all language objects filled up by default values
  76
+    """
  77
+    site_id = get_site(site_id)
  78
+    languages = []
  79
+    for language in settings.CMS_LANGUAGES[site_id]:
  80
+        languages.append(get_language_object(language['code'], site_id))
  81
+    return languages
  82
+
  83
+
5 84
 def get_default_language(language_code=None):
6  
-    """Returns default language depending on settings.LANGUAGE_CODE merged with
  85
+    """
  86
+    Returns default language depending on settings.LANGUAGE_CODE merged with
7 87
     best match from settings.CMS_LANGUAGES
8  
-    
  88
+
9 89
     Returns: language_code
10 90
     """
11  
-    
  91
+
12 92
     if not language_code:
13 93
         language_code = settings.LANGUAGE_CODE
14  
-    
15  
-    languages = dict(settings.CMS_LANGUAGES).keys()
16  
-    
  94
+
  95
+    languages = get_language_list()
  96
+
17 97
     # first try if there is an exact language
18 98
     if language_code in languages:
19 99
         return language_code
20  
-    
  100
+
21 101
     # otherwise split the language code if possible, so iso3
22 102
     language_code = language_code.split("-")[0]
23  
-    
  103
+
24 104
     if not language_code in languages:
25 105
         return settings.LANGUAGE_CODE
26  
-    
  106
+
27 107
     return language_code
28 108
 
29  
-def get_fallback_languages(language):
  109
+
  110
+def get_fallback_languages(language, site_id=None):
30 111
     """
31 112
     returns a list of fallback languages for the given language
32 113
     """
33  
-    conf = settings.CMS_LANGUAGE_CONF
34  
-    if language in conf:
35  
-        l_list = conf[language]
36  
-    else:
37  
-        languages = settings.CMS_LANGUAGES
38  
-        l_list = []
39  
-        for lang in languages:
40  
-            l_list.append(lang[0])
41  
-    if language in l_list:
42  
-        l_list.remove(language)
43  
-    return l_list
  114
+    site_id = get_site(site_id)
  115
+    language = get_language_object(language, site_id)
  116
+    return language.get('fallbacks', [])
44 117
 
  118
+def get_redirect_on_fallback(language, site_id=None):
  119
+    """
  120
+    returns if you should redirect on language fallback
  121
+    :param language:
  122
+    :param site_id:
  123
+    :return: Boolean
  124
+    """
  125
+    site_id = get_site(site_id)
  126
+    language = get_language_object(language, site_id)
  127
+    return language.get('redirect_on_fallback', True)
45 128
 
  129
+def hide_untranslated(language, site_id=None):
  130
+    """
  131
+    Should untranslated pages in this language be hidden?
  132
+    :param language:
  133
+    :param site_id:
  134
+    :return: A Boolean
  135
+    """
  136
+    site_id = get_site(site_id)
  137
+    language = get_language_object(language, site_id)
  138
+    return language.get('hide_untranslated', True)
46 139
 
47  
-class force_language(object):
48  
-    def __init__(self, new_lang):
49  
-        self.new_lang = new_lang
50  
-        self.old_lang = translation.get_language()
51  
-    def __enter__(self):
52  
-        translation.activate(self.new_lang)
53  
-    def __exit__(self, type, value, tb):
54  
-        translation.activate(self.old_lang)
  140
+def get_site(site):
  141
+    if site is None:
  142
+        return settings.SITE_ID
  143
+    else:
  144
+        try:
  145
+            return int(site)
  146
+        except TypeError:
  147
+            return site.pk
  148
+
  149
+
  150
+class LanguageError(Exception):
  151
+    pass
32  cms/views.py
@@ -2,8 +2,9 @@
2 2
 from __future__ import with_statement
3 3
 from cms.apphook_pool import apphook_pool
4 4
 from cms.appresolver import get_app_urls
  5
+from cms.models import Title
5 6
 from cms.utils import get_template_from_request, get_language_from_request
6  
-from cms.utils.i18n import get_fallback_languages, force_language
  7
+from cms.utils.i18n import get_fallback_languages, force_language, get_public_languages, get_redirect_on_fallback
7 8
 from cms.utils.page_resolver import get_page_from_request
8 9
 from cms.test_utils.util.context_managers import SettingsOverride
9 10
 from django.conf import settings
@@ -38,11 +39,11 @@ def details(request, slug):
38 39
 
39 40
     available_languages = []
40 41
     page_languages = page.get_languages()
41  
-    for frontend_lang in settings.CMS_FRONTEND_LANGUAGES:
  42
+    for frontend_lang in get_public_languages():
42 43
         if frontend_lang in page_languages:
43 44
             available_languages.append(frontend_lang)
44 45
     # Check that the language is in FRONTEND_LANGUAGES:
45  
-    if not current_language in settings.CMS_FRONTEND_LANGUAGES:
  46
+    if not current_language in get_public_languages():
46 47
         #are we on root?
47 48
         if not slug:
48 49
             #redirect to supported language
@@ -60,24 +61,24 @@ def details(request, slug):
60 61
                 _handle_no_page(request, slug)
61 62
         else:
62 63
             return _handle_no_page(request, slug)
63  
-    # We resolve an alternate language for the page if it's not available.
64  
-    # Since the "old" details view had an exception for the root page, it is
65  
-    # ported here. So no resolution if the slug is ''.
66 64
     if current_language not in available_languages:
67  
-        if settings.CMS_LANGUAGE_FALLBACK:
68  
-            # If we didn't find the required page in the requested (current) 
69  
-            # language, let's try to find a suitable fallback in the list of 
70  
-            # fallback languages (CMS_LANGUAGE_CONF)
71  
-            for alt_lang in get_fallback_languages(current_language):
72  
-                if alt_lang in available_languages:
  65
+        # If we didn't find the required page in the requested (current)
  66
+        # language, let's try to find a fallback
  67
+        found = False
  68
+        for alt_lang in get_fallback_languages(current_language):
  69
+            if alt_lang in available_languages:
  70
+                if get_redirect_on_fallback(current_language):
73 71
                     with force_language(alt_lang):
74 72
                         path = page.get_absolute_url(language=alt_lang, fallback=True)
75 73
                         # In the case where the page is not available in the
76 74
                     # preferred language, *redirect* to the fallback page. This
77 75
                     # is a design decision (instead of rendering in place)).
78 76
                     return HttpResponseRedirect(path)
  77
+                else:
  78