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

parler._get_cached_values()   F

Complexity

Conditions 9

Size

Total Lines 33

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 9
dl 0
loc 33
rs 3
1
"""
2
django-parler uses caching to avoid fetching model data when it doesn't have to.
3
4
These functions are used internally by django-parler to fetch model data.
5
Since all calls to the translation table are routed through our model descriptor fields,
6
cache access and expiry is rather simple to implement.
7
"""
8
import django
9
from django.core.cache import cache
10
from django.utils import six
11
from parler import appsettings
12
from parler.utils import get_language_settings
13
14
if six.PY3:
15
    long = int
16
17
if django.VERSION >= (1, 6):
18
    DEFAULT_TIMEOUT = cache.default_timeout
19
else:
20
    DEFAULT_TIMEOUT = 0
21
22
23
class IsMissing(object):
24
    # Allow _get_any_translated_model() to evaluate this as False.
25
26
    def __nonzero__(self):
27
        return False   # Python 2
28
29
    def __bool__(self):
30
        return False   # Python 3
31
32
    def __repr__(self):
33
        return "<IsMissing>"
34
35
MISSING = IsMissing()  # sentinel value
36
37
38
def get_object_cache_keys(instance):
39
    """
40
    Return the cache keys associated with an object.
41
    """
42
    if instance.pk is None or instance._state.adding:
43
        return []
44
45
    keys = []
46
    tr_models = instance._parler_meta.get_all_models()
47
48
    # TODO: performs a query to fetch the language codes. Store that in memcached too.
49
    for language in instance.get_available_languages():
50
        for tr_model in tr_models:
51
            keys.append(get_translation_cache_key(tr_model, instance.pk, language))
52
53
    return keys
54
55
56
def get_translation_cache_key(translated_model, master_id, language_code):
57
    """
58
    The low-level function to get the cache key for a translation.
59
    """
60
    # Always cache the entire object, as this already produces
61
    # a lot of queries. Don't go for caching individual fields.
62
    return 'parler.{0}.{1}.{2}.{3}'.format(translated_model._meta.app_label, translated_model.__name__, master_id, language_code)
63
64
65
def get_cached_translation(instance, language_code=None, related_name=None, use_fallback=False):
66
    """
67
    Fetch an cached translation.
68
69
    .. versionadded 1.2 Added the ``related_name`` parameter.
70
    """
71
    if language_code is None:
72
        language_code = instance.get_current_language()
73
74
    translated_model = instance._parler_meta.get_model_by_related_name(related_name)
75
    values = _get_cached_values(instance, translated_model, language_code, use_fallback)
76
    if not values:
77
        return None
78
79
    try:
80
        translation = translated_model(**values)
81
    except TypeError:
82
        # Some model field was removed, cache entry is no longer working.
83
        return None
84
85
    translation._state.adding = False
86
    return translation
87
88
89
def get_cached_translated_field(instance, field_name, language_code=None, use_fallback=False):
90
    """
91
    Fetch an cached field.
92
    """
93
    if language_code is None:
94
        language_code = instance.get_current_language()
95
96
    # In django-parler 1.1 the order of the arguments was fixed, It used to be language_code, field_name
97
    # This serves as detection against backwards incompatibility issues.
98
    if len(field_name) <= 5 and len(language_code) > 5:
99
        raise RuntimeError("Unexpected language code, did you swap field_name, language_code?")
100
101
    translated_model = instance._parler_meta.get_model_by_field(field_name)
102
    values = _get_cached_values(instance, translated_model, language_code, use_fallback)
103
    if not values:
104
        return None
105
106
    # Allow older cached versions where the field didn't exist yet.
107
    return values.get(field_name, None)
108
109
110
def _get_cached_values(instance, translated_model, language_code, use_fallback=False):
111
    """
112
    Fetch an cached field.
113
    """
114
    if not appsettings.PARLER_ENABLE_CACHING or not instance.pk or instance._state.adding:
115
        return None
116
117
    key = get_translation_cache_key(translated_model, instance.pk, language_code)
118
    values = cache.get(key)
119
    if not values:
120
        return None
121
122
    # Check for a stored fallback marker
123
    if values.get('__FALLBACK__', False):
124
        # Internal trick, already set the fallback marker, so no query will be performed.
125
        instance._translations_cache[translated_model][language_code] = MISSING
126
127
        # Allow to return the fallback language instead.
128
        if use_fallback:
129
            lang_dict = get_language_settings(language_code)
130
            # iterate over list of fallback languages, which should be already
131
            # in proper order
132
            for fallback_lang in lang_dict['fallbacks']:
133
                if fallback_lang != language_code:
134
                    return _get_cached_values(
135
                        instance, translated_model, fallback_lang,
136
                        use_fallback=False
137
                    )
138
        return None
139
140
    values['master'] = instance
141
    values['language_code'] = language_code
142
    return values
143
144
145
def _cache_translation(translation, timeout=DEFAULT_TIMEOUT):
146
    """
147
    Store a new translation in the cache.
148
    """
149
    if not appsettings.PARLER_ENABLE_CACHING:
150
        return
151
152
    if translation.master_id is None:
153
        raise ValueError("Can't cache unsaved translation")
154
155
    # Cache a translation object.
156
    # For internal usage, object parameters are not suited for outside usage.
157
    fields = translation.get_translated_fields()
158
    values = {'id': translation.id}
159
    for name in fields:
160
        values[name] = getattr(translation, name)
161
162
    key = get_translation_cache_key(translation.__class__, translation.master_id, translation.language_code)
163
    cache.set(key, values, timeout=timeout)
164
165
166
def _cache_translation_needs_fallback(instance, language_code, related_name, timeout=DEFAULT_TIMEOUT):
167
    """
168
    Store the fact that a translation doesn't exist, and the fallback should be used.
169
    """
170
    if not appsettings.PARLER_ENABLE_CACHING or not instance.pk or instance._state.adding:
171
        return
172
173
    tr_model = instance._parler_meta.get_model_by_related_name(related_name)
174
    key = get_translation_cache_key(tr_model, instance.pk, language_code)
175
    cache.set(key, {'__FALLBACK__': True}, timeout=timeout)
176
177
178
def _delete_cached_translations(shared_model):
179
    cache.delete_many(get_object_cache_keys(shared_model))
180
181
182
def _delete_cached_translation(translation):
183
    if not appsettings.PARLER_ENABLE_CACHING:
184
        return
185
186
    # Delete a cached translation
187
    # For internal usage, object parameters are not suited for outside usage.
188
    key = get_translation_cache_key(translation.__class__, translation.master_id, translation.language_code)
189
    cache.delete(key)
190