Completed
Push — master ( 0bffe1...d6203d )
by Diederik van der
11s
created

parler.utils.get_parler_languages_from_django_cms()   F

Complexity

Conditions 13

Size

Total Lines 34

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 13
dl 0
loc 34
rs 2.7716

How to fix   Complexity   

Complexity

Complex classes like parler.utils.get_parler_languages_from_django_cms() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""
2
The configuration wrappers that are used for :ref:`PARLER_LANGUAGES`.
3
"""
4
5
import copy
6
import sys
7
8
from django.conf import settings
9
from django.core.exceptions import ImproperlyConfigured
10
from django.utils import six
11
from django.utils.translation import get_language
12
from parler.utils.i18n import is_supported_django_language
13
14
15
def add_default_language_settings(languages_list, var_name='PARLER_LANGUAGES', **extra_defaults):
16
    """
17
    Apply extra defaults to the language settings.
18
    This function can also be used by other packages to
19
    create their own variation of ``PARLER_LANGUAGES`` with extra fields.
20
    For example::
21
22
        from django.conf import settings
23
        from parler import appsettings as parler_appsettings
24
25
        # Create local names, which are based on the global parler settings
26
        MYAPP_DEFAULT_LANGUAGE_CODE = getattr(settings, 'MYAPP_DEFAULT_LANGUAGE_CODE', parler_appsettings.PARLER_DEFAULT_LANGUAGE_CODE)
27
        MYAPP_LANGUAGES = getattr(settings, 'MYAPP_LANGUAGES', parler_appsettings.PARLER_LANGUAGES)
28
29
        # Apply the defaults to the languages
30
        MYAPP_LANGUAGES = parler_appsettings.add_default_language_settings(MYAPP_LANGUAGES, 'MYAPP_LANGUAGES',
31
            code=MYAPP_DEFAULT_LANGUAGE_CODE,
32
            fallback=MYAPP_DEFAULT_LANGUAGE_CODE,
33
            hide_untranslated=False
34
        )
35
36
    The returned object will be an :class:`~parler.utils.conf.LanguagesSetting` object,
37
    which adds additional methods to the :class:`dict` object.
38
39
    :param languages_list: The settings, in :ref:`PARLER_LANGUAGES` format.
40
    :param var_name: The name of your variable, for debugging output.
41
    :param extra_defaults: Any defaults to override in the ``languages_list['default']`` section, e.g. ``code``, ``fallback``, ``hide_untranslated``.
42
    :return: The updated ``languages_list`` with all defaults applied to all sections.
43
    :rtype: LanguagesSetting
44
    """
45
    languages_list = LanguagesSetting(languages_list)
46
47
    languages_list.setdefault('default', {})
48
    defaults = languages_list['default']
49
    defaults.setdefault('hide_untranslated', False)   # Whether queries with .active_translations() may or may not return the fallback language.
50
51
    if 'fallback' in defaults:
52
        #warnings.warn("Please use 'fallbacks' instead of 'fallback' in the 'defaults' section of {0}".format(var_name), DeprecationWarning)
53
        defaults['fallbacks'] = [defaults.pop('fallback')]
54
    if 'fallback' in extra_defaults:
55
        #warnings.warn("Please use 'fallbacks' instead of 'fallback' in parameters for {0} = add_default_language_settings(..)".format(var_name), DeprecationWarning)
56
        extra_defaults['fallbacks'] = [extra_defaults.pop('fallback')]
57
58
    defaults.update(extra_defaults)  # Also allow to override code and fallback this way.
59
60
    # This function previously existed in appsettings, where it could reference the defaults directly.
61
    # However, this module is a more logical place for this function. To avoid circular import problems,
62
    # the 'code' and 'fallback' parameters are always passed by the appsettings module.
63
    # In case these are missing, default to the original behavior for backwards compatibility.
64
    if 'code' not in defaults:
65
        from parler import appsettings
66
        defaults['code'] = appsettings.PARLER_DEFAULT_LANGUAGE_CODE
67
    if 'fallbacks' not in defaults:
68
        from parler import appsettings
69
        defaults['fallbacks'] = [appsettings.PARLER_DEFAULT_LANGUAGE_CODE]
70
71
    if not is_supported_django_language(defaults['code']):
72
        raise ImproperlyConfigured("The value for {0}['defaults']['code'] ('{1}') does not exist in LANGUAGES".format(var_name, defaults['code']))
73
74
    for site_id, lang_choices in six.iteritems(languages_list):
75
        if site_id == 'default':
76
            continue
77
78
        if not isinstance(lang_choices, (list, tuple)):
79
            raise ImproperlyConfigured("{0}[{1}] should be a tuple of language choices!".format(var_name, site_id))
80
        for i, choice in enumerate(lang_choices):
81
            if not is_supported_django_language(choice['code']):
82
                raise ImproperlyConfigured("{0}[{1}][{2}]['code'] does not exist in LANGUAGES".format(var_name, site_id, i))
83
84
            # Copy all items from the defaults, so you can provide new fields too.
85
            for key, value in six.iteritems(defaults):
86
                choice.setdefault(key, value)
87
88
    return languages_list
89
90
91
class LanguagesSetting(dict):
92
    """
93
    This is the actual object type of the :ref:`PARLER_LANGUAGES` setting.
94
    Besides the regular :class:`dict` behavior, it also adds some additional methods.
95
    """
96
97
    def get_language(self, language_code, site_id=None):
98
        """
99
        Return the language settings for the current site
100
101
        This function can be used with other settings variables
102
        to support modules which create their own variation of the ``PARLER_LANGUAGES`` setting.
103
        For an example, see :func:`~parler.appsettings.add_default_language_settings`.
104
        """
105
        if language_code is None:
106
            # This happens when using parler in management commands.
107
            # Use translation.activate('en') if you need to have a default locale active.
108
            if get_language() is None:
109
                raise ValueError("language_code can't be null, use translation.activate(..) when accessing translated models outside the request/response loop.")
110
            else:
111
                raise ValueError("language_code can't be null")
112
113
        if site_id is None:
114
            site_id = getattr(settings, 'SITE_ID', None)
115
116
        for lang_dict in self.get(site_id, ()):
117
            if lang_dict['code'] == language_code:
118
                return lang_dict
119
120
        # no language match, search for variant: fr-ca falls back to fr
121
        for lang_dict in self.get(site_id, ()):
122
            if lang_dict['code'].split('-')[0] == language_code.split('-')[0]:
123
                return lang_dict
124
125
        return self['default']
126
127
    def get_active_choices(self, language_code=None, site_id=None):
128
        """
129
        Find out which translations should be visible in the site.
130
        It returns a list with either a single choice (the current language),
131
        or a list with the current language + fallback language.
132
        """
133
        if language_code is None:
134
            language_code = get_language()
135
136
        lang_dict = self.get_language(language_code, site_id=site_id)
137
        if not lang_dict['hide_untranslated']:
138
            return [language_code] + [lang for lang in lang_dict['fallbacks'] if lang != language_code]
139
        else:
140
            return [language_code]
141
142
    def get_fallback_languages(self, language_code=None, site_id=None):
143
        """
144
        Find out what the fallback language is for a given language choice.
145
146
        .. versionadded 1.5
147
        """
148
        choices = self.get_active_choices(language_code, site_id=site_id)
149
        return choices[1:]
150
151
    def get_fallback_language(self, language_code=None, site_id=None):
152
        """
153
        Find out what the fallback language is for a given language choice.
154
155
        .. deprecated:: 1.5
156
           Use :func:`get_fallback_languages` instead.
157
        """
158
        choices = self.get_active_choices(language_code, site_id=site_id)
159
        if choices and len(choices) > 1:
160
            # Still take the last, like previous code.
161
            # With multiple fallback languages that means taking the base language.
162
            # Hence, upgrade the code to use get_fallback_languages() instead.
163
            return choices[-1]
164
        else:
165
            return None
166
167
    def get_default_language(self):
168
        """
169
        Return the default language.
170
        """
171
        return self['default']['code']
172
173
    def get_first_language(self, site_id=None):
174
        """
175
        Return the first language for the current site.
176
        This can be used for user interfaces, where the languages are displayed in tabs.
177
        """
178
        if site_id is None:
179
            site_id = getattr(settings, 'SITE_ID', None)
180
181
        try:
182
            return self[site_id][0]['code']
183
        except (KeyError, IndexError):
184
            # No configuration, always fallback to default language.
185
            # This is essentially a non-multilingual configuration.
186
            return self['default']['code']
187
188
189
def get_parler_languages_from_django_cms(cms_languages=None):
190
    """
191
    Converts django CMS' setting CMS_LANGUAGES into PARLER_LANGUAGES. Since
192
    CMS_LANGUAGES is a strict superset of PARLER_LANGUAGES, we do a bit of
193
    cleansing to remove irrelevant items.
194
    """
195
    valid_keys = ['code', 'fallbacks', 'hide_untranslated',
196
                  'redirect_on_fallback']
197
    if cms_languages:
198
        if sys.version_info < (3, 0, 0):
199
            int_types = (int, long)
200
        else:
201
            int_types = int
202
203
        parler_languages = copy.deepcopy(cms_languages)
204
        for site_id, site_config in cms_languages.items():
205
            if site_id and (
206
                    not isinstance(site_id, int_types) and
207
                    site_id != 'default'
208
            ):
209
                del parler_languages[site_id]
210
                continue
211
212
            if site_id == 'default':
213
                for key, value in site_config.items():
214
                    if key not in valid_keys:
215
                        del parler_languages['default'][key]
216
            else:
217
                for i, lang_config in enumerate(site_config):
218
                    for key, value in lang_config.items():
219
                        if key not in valid_keys:
220
                            del parler_languages[site_id][i][key]
221
        return parler_languages
222
    return None
223