ParlerOptions.get_fields_with_model()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
c 0
b 0
f 0
rs 9.4285
cc 1
1
"""
2
The models and fields for translation support.
3
4
The default is to use the :class:`TranslatedFields` class in the model, like:
5
6
.. code-block:: python
7
8
    from django.db import models
9
    from django.utils.encoding import python_2_unicode_compatible
10
    from parler.models import TranslatableModel, TranslatedFields
11
12
13
    @python_2_unicode_compatible
14
    class MyModel(TranslatableModel):
15
        translations = TranslatedFields(
16
            title = models.CharField(_("Title"), max_length=200)
17
        )
18
19
        class Meta:
20
            verbose_name = _("MyModel")
21
22
        def __str__(self):
23
            return self.title
24
25
26
It's also possible to create the translated fields model manually:
27
28
.. code-block:: python
29
30
    from django.db import models
31
    from django.utils.encoding import python_2_unicode_compatible
32
    from parler.models import TranslatableModel, TranslatedFieldsModel
33
    from parler.fields import TranslatedField
34
35
36
    class MyModel(TranslatableModel):
37
        title = TranslatedField()  # Optional, explicitly mention the field
38
39
        class Meta:
40
            verbose_name = _("MyModel")
41
42
        def __str__(self):
43
            return self.title
44
45
46
    class MyModelTranslation(TranslatedFieldsModel):
47
        master = models.ForeignKey(MyModel, related_name='translations', null=True)
48
        title = models.CharField(_("Title"), max_length=200)
49
50
        class Meta:
51
            verbose_name = _("MyModel translation")
52
53
This has the same effect, but also allows to to override
54
the :func:`~django.db.models.Model.save` method, or add new methods yourself.
55
56
The translated model is compatible with django-hvad, making the transition between both projects relatively easy.
57
The manager and queryset objects of django-parler can work together with django-mptt and django-polymorphic.
58
"""
59
from __future__ import unicode_literals
60
from collections import defaultdict
61
import django
62
from django.conf import settings
63
from django.core.exceptions import ImproperlyConfigured, ValidationError, FieldError, ObjectDoesNotExist
64
from django.db import models, router
65
from django.db.models.base import ModelBase
66
from django.utils.encoding import python_2_unicode_compatible
67
from django.utils.functional import lazy
68
from django.utils.translation import get_language, ugettext, ugettext_lazy as _
69
from django.utils import six
70
from parler import signals
71
from parler.cache import MISSING, _cache_translation, _cache_translation_needs_fallback, _delete_cached_translation, get_cached_translation, _delete_cached_translations, get_cached_translated_field
72
from parler.fields import TranslatedField, LanguageCodeDescriptor, TranslatedFieldDescriptor
73
from parler.managers import TranslatableManager
74
from parler.utils import compat
75
from parler.utils.i18n import normalize_language_code, get_language_settings, get_language_title, get_null_language_error
76
import sys
77
78
try:
79
    from collections import OrderedDict
80
except ImportError:
81
    from django.utils.datastructures import SortedDict as OrderedDict
82
83
if django.VERSION >= (1, 9):
84
    from django.db.models.fields.related_descriptors import ForwardManyToOneDescriptor
85
else:
86
    from django.db.models.fields.related import ReverseSingleRelatedObjectDescriptor as ForwardManyToOneDescriptor
87
88
__all__ = (
89
    'TranslatableModelMixin',
90
    'TranslatableModel',
91
    'TranslatedFields',
92
    'TranslatedFieldsModel',
93
    'TranslatedFieldsModelBase',
94
    'TranslationDoesNotExist',
95
    #'create_translations_model',
96
)
97
98
99
class TranslationDoesNotExist(AttributeError, ObjectDoesNotExist):
100
    """
101
    A tagging interface to detect missing translations.
102
    The exception inherits from :class:`~exceptions.AttributeError` to reflect what is actually happening.
103
    Therefore it also causes the templates to handle the missing attributes silently, which is very useful in the admin for example.
104
    The exception also inherits from :class:`~django.core.exceptions.ObjectDoesNotExist`,
105
    so any code that checks for this can deal with missing translations out of the box.
106
107
    This class is also used in the ``DoesNotExist`` object on the translated model, which inherits from:
108
109
    * this class
110
    * the ``sharedmodel.DoesNotExist`` class
111
    * the original ``translatedmodel.DoesNotExist`` class.
112
113
    This makes sure that the regular code flow is decently handled by existing exception handlers.
114
    """
115
    pass
116
117
118
_lazy_verbose_name = lazy(lambda x: ugettext("{0} Translation").format(x._meta.verbose_name), six.text_type)
119
120
121
def create_translations_model(shared_model, related_name, meta, **fields):
122
    """
123
    Dynamically create the translations model.
124
    Create the translations model for the shared model 'model'.
125
126
    :param related_name: The related name for the reverse FK from the translations model.
127
    :param meta: A (optional) dictionary of attributes for the translations model's inner Meta class.
128
    :param fields: A dictionary of fields to put on the translations model.
129
130
    Two fields are enforced on the translations model:
131
132
        language_code: A 15 char, db indexed field.
133
        master: A ForeignKey back to the shared model.
134
135
    Those two fields are unique together.
136
    """
137
    if not meta:
138
        meta = {}
139
140
    if shared_model._meta.abstract:
141
        # This can't be done, because `master = ForeignKey(shared_model)` would fail.
142
        raise TypeError("Can't create TranslatedFieldsModel for abstract class {0}".format(shared_model.__name__))
143
144
    # Define inner Meta class
145
    meta['app_label'] = shared_model._meta.app_label
146
    meta['db_tablespace'] = shared_model._meta.db_tablespace
147
    meta['managed'] = shared_model._meta.managed
148
    meta['unique_together'] = list(meta.get('unique_together', [])) + [('language_code', 'master')]
149
    meta.setdefault('db_table', '{0}_translation'.format(shared_model._meta.db_table))
150
    meta.setdefault('verbose_name', _lazy_verbose_name(shared_model))
151
152
    # Avoid creating permissions for the translated model, these are not used at all.
153
    # This also avoids creating lengthy permission names above 50 chars.
154
    if django.VERSION >= (1, 7):
155
        meta.setdefault('default_permissions', ())
156
157
    # Define attributes for translation table
158
    name = str('{0}Translation'.format(shared_model.__name__))  # makes it bytes, for type()
159
160
    attrs = {}
161
    attrs.update(fields)
162
    attrs['Meta'] = type(str('Meta'), (object,), meta)
163
    attrs['__module__'] = shared_model.__module__
164
    attrs['objects'] = models.Manager()
165
    attrs['master'] = models.ForeignKey(shared_model, related_name=related_name, editable=False, null=True)
166
167
    # Create and return the new model
168
    translations_model = TranslatedFieldsModelBase(name, (TranslatedFieldsModel,), attrs)
169
170
    # Register it as a global in the shared model's module.
171
    # This is needed so that Translation model instances, and objects which refer to them, can be properly pickled and unpickled.
172
    # The Django session and caching frameworks, in particular, depend on this behaviour.
173
    mod = sys.modules[shared_model.__module__]
174
    setattr(mod, name, translations_model)
175
176
    return translations_model
177
178
179
class TranslatedFields(object):
180
    """
181
    Wrapper class to define translated fields on a model.
182
183
    The field name becomes the related name of the :class:`TranslatedFieldsModel` subclass.
184
185
    Example:
186
187
    .. code-block:: python
188
189
        from django.db import models
190
        from parler.models import TranslatableModel, TranslatedFields
191
192
        class MyModel(TranslatableModel):
193
            translations = TranslatedFields(
194
                title = models.CharField("Title", max_length=200)
195
            )
196
197
    When the class is initialized, the attribute will point
198
    to a :class:`~django.db.models.fields.related.ForeignRelatedObjectsDescriptor` object.
199
    Hence, accessing ``MyModel.translations.related.related_model`` returns the original model
200
    via the :class:`django.db.models.related.RelatedObject` class.
201
202
    ..
203
       To fetch the attribute, you can also query the Parler metadata:
204
       MyModel._parler_meta.get_model_by_related_name('translations')
205
    """
206
207
    def __init__(self, meta=None, **fields):
208
        self.fields = fields
209
        self.meta = meta
210
        self.name = None
211
212
    def contribute_to_class(self, cls, name):
213
        # Called from django.db.models.base.ModelBase.__new__
214
        self.name = name
215
        create_translations_model(cls, name, self.meta, **self.fields)
216
217
218
class TranslatableModelMixin(object):
219
    """
220
    Base model mixin class to handle translations.
221
222
    All translatable fields will appear on this model, proxying the calls to the :class:`TranslatedFieldsModel`.
223
    """
224
    #: Access to the metadata of the translatable model
225
    _parler_meta = None
226
227
    #: Access to the language code
228
    language_code = LanguageCodeDescriptor()
229
230
    def __init__(self, *args, **kwargs):
231
        # Still allow to pass the translated fields (e.g. title=...) to this function.
232
        translated_kwargs = {}
233
        current_language = None
234
        if kwargs:
235
            current_language = kwargs.pop('_current_language', None)
236
            for field in self._parler_meta.get_all_fields():
237
                try:
238
                    translated_kwargs[field] = kwargs.pop(field)
239
                except KeyError:
240
                    pass
241
242
        # Have the attributes available, but they can't be ready yet;
243
        # self._state.adding is always True at this point,
244
        # the QuerySet.iterator() code changes it after construction.
245
        self._translations_cache = None
246
        self._current_language = None
247
248
        # Run original Django model __init__
249
        super(TranslatableModelMixin, self).__init__(*args, **kwargs)
250
251
        # Assign translated args manually.
252
        self._translations_cache = defaultdict(dict)
253
        self._current_language = normalize_language_code(current_language or get_language())  # What you used to fetch the object is what you get.
254
255
        if translated_kwargs:
256
            self._set_translated_fields(self._current_language, **translated_kwargs)
257
258
    def _set_translated_fields(self, language_code=None, **fields):
259
        """
260
        Assign fields to the translated models.
261
        """
262
        objects = []  # no generator, make sure objects are all filled first
263
        for parler_meta, model_fields in self._parler_meta._split_fields(**fields):
264
            translation = self._get_translated_model(language_code=language_code, auto_create=True, meta=parler_meta)
265
            for field, value in six.iteritems(model_fields):
266
                setattr(translation, field, value)
267
268
            objects.append(translation)
269
        return objects
270
271
    def create_translation(self, language_code, **fields):
272
        """
273
        Add a translation to the model.
274
275
        The :func:`save_translations` function is called afterwards.
276
277
        The object will be saved immediately, similar to
278
        calling :func:`~django.db.models.manager.Manager.create`
279
        or :func:`~django.db.models.fields.related.RelatedManager.create` on related fields.
280
        """
281
        if language_code is None:
282
            raise ValueError(get_null_language_error())
283
284
        meta = self._parler_meta
285
        if self._translations_cache[meta.root_model].get(language_code, None):  # MISSING evaluates to False too
286
            raise ValueError("Translation already exists: {0}".format(language_code))
287
288
        # Save all fields in the proper translated model.
289
        for translation in self._set_translated_fields(language_code, **fields):
290
            self.save_translation(translation)
291
292
    def get_current_language(self):
293
        """
294
        Get the current language.
295
        """
296
        # not a property, so won't conflict with model fields.
297
        return self._current_language
298
299
    def set_current_language(self, language_code, initialize=False):
300
        """
301
        Switch the currently activate language of the object.
302
        """
303
        self._current_language = normalize_language_code(language_code or get_language())
304
305
        # Ensure the translation is present for __get__ queries.
306
        if initialize:
307
            self._get_translated_model(use_fallback=False, auto_create=True)
308
309
    def get_fallback_language(self):
310
        """
311
        .. deprecated:: 1.5
312
           Use :func:`get_fallback_languages` instead.
313
        """
314
        fallbacks = self.get_fallback_languages()
315
        return fallbacks[0] if fallbacks else None
316
317
    def get_fallback_languages(self):
318
        """
319
        Return the fallback language codes,
320
        which are used in case there is no translation for the currently active language.
321
        """
322
        lang_dict = get_language_settings(self._current_language)
323
        fallbacks = [lang for lang in lang_dict['fallbacks'] if lang != self._current_language]
324
        return fallbacks or []
325
326
    def has_translation(self, language_code=None, related_name=None):
327
        """
328
        Return whether a translation for the given language exists.
329
        Defaults to the current language code.
330
331
        .. versionadded 1.2 Added the ``related_name`` parameter.
332
        """
333
        if language_code is None:
334
            language_code = self._current_language
335
            if language_code is None:
336
                raise ValueError(get_null_language_error())
337
338
        meta = self._parler_meta._get_extension_by_related_name(related_name)
339
340
        try:
341
            # Check the local cache directly, and the answer is known.
342
            # NOTE this may also return newly auto created translations which are not saved yet.
343
            return self._translations_cache[meta.model][language_code] is not MISSING
344
        except KeyError:
345
            # If there is a prefetch, will be using that.
346
            # However, don't assume the prefetch contains all possible languages.
347
            # With Django 1.8, there are custom Prefetch objects.
348
            # TODO: improve this, detect whether this is the case.
349
            if language_code in self._read_prefetched_translations(meta=meta):
350
                return True
351
352
            # Try to fetch from the cache first.
353
            # If the cache returns the fallback, it means the original does not exist.
354
            object = get_cached_translation(self, language_code, related_name=related_name, use_fallback=True)
355
            if object is not None:
356
                return object.language_code == language_code
357
358
            try:
359
                # Fetch from DB, fill the cache.
360
                self._get_translated_model(language_code, use_fallback=False, auto_create=False, meta=meta)
361
            except meta.model.DoesNotExist:
362
                return False
363
            else:
364
                return True
365
366
    def get_available_languages(self, related_name=None, include_unsaved=False):
367
        """
368
        Return the language codes of all translated variations.
369
370
        .. versionadded 1.2 Added the ``include_unsaved`` and ``related_name`` parameters.
371
        """
372
        meta = self._parler_meta._get_extension_by_related_name(related_name)
373
374
        prefetch = self._get_prefetched_translations(meta=meta)
375
        if prefetch is not None:
376
            # TODO: this will break when using custom Django 1.8 Prefetch objects?
377
            db_languages = sorted(obj.language_code for obj in prefetch)
378
        else:
379
            qs = self._get_translated_queryset(meta=meta)
380
            db_languages = qs.values_list('language_code', flat=True).order_by('language_code')
381
382
        if include_unsaved:
383
            local_languages = (k for k, v in six.iteritems(self._translations_cache[meta.model]) if v is not MISSING)
384
            return list(set(db_languages) | set(local_languages))
385
        else:
386
            return db_languages
387
388
    def get_translation(self, language_code, related_name=None):
389
        """
390
        Fetch the translated model
391
        """
392
        meta = self._parler_meta._get_extension_by_related_name(related_name)
393
        return self._get_translated_model(language_code, meta=meta)
394
395
    def _get_translated_model(self, language_code=None, use_fallback=False, auto_create=False, meta=None):
396
        """
397
        Fetch the translated fields model.
398
        """
399
        if self._parler_meta is None:
400
            raise ImproperlyConfigured("No translation is assigned to the current model!")
401
        if self._translations_cache is None:
402
            raise RuntimeError("Accessing translated fields before super.__init__() is not possible.")
403
404
        if not language_code:
405
            language_code = self._current_language
406
            if language_code is None:
407
                raise ValueError(get_null_language_error())
408
409
        if meta is None:
410
            meta = self._parler_meta.root  # work on base model by default
411
412
        local_cache = self._translations_cache[meta.model]
413
414
        # 1. fetch the object from the local cache
415
        try:
416
            object = local_cache[language_code]
417
418
            # If cached object indicates the language doesn't exist, need to query the fallback.
419
            if object is not MISSING:
420
                return object
421
        except KeyError:
422
            # 2. No cache, need to query
423
            # Check that this object already exists, would be pointless otherwise to check for a translation.
424
            if not self._state.adding and self.pk is not None:
425
                prefetch = self._get_prefetched_translations(meta=meta)
426
                if prefetch is not None:
427
                    # 2.1, use prefetched data
428
                    # If the object is not found in the prefetched data (which contains all translations),
429
                    # it's pointless to check for memcached (2.2) or perform a single query (2.3)
430
                    for object in prefetch:
431
                        if object.language_code == language_code:
432
                            local_cache[language_code] = object
433
                            _cache_translation(object)  # Store in memcached
434
                            return object
435
                else:
436
                    # 2.2, fetch from memcached
437
                    object = get_cached_translation(self, language_code, related_name=meta.rel_name, use_fallback=use_fallback)
438
                    if object is not None:
439
                        # Track in local cache
440
                        if object.language_code != language_code:
441
                            local_cache[language_code] = MISSING  # Set fallback marker
442
                        local_cache[object.language_code] = object
443
                        return object
444
                    elif local_cache.get(language_code, None) is MISSING:
445
                        # If get_cached_translation() explicitly set the "does not exist" marker,
446
                        # there is no need to try a database query.
447
                        pass
448
                    else:
449
                        # 2.3, fetch from database
450
                        try:
451
                            object = self._get_translated_queryset(meta).get(language_code=language_code)
452
                        except meta.model.DoesNotExist:
453
                            pass
454
                        else:
455
                            local_cache[language_code] = object
456
                            _cache_translation(object)  # Store in memcached
457
                            return object
458
459
        # Not in cache, or default.
460
        # Not fetched from DB
461
462
        # 3. Auto create?
463
        if auto_create:
464
            # Auto create policy first (e.g. a __set__ call)
465
            kwargs = {
466
                'language_code': language_code,
467
            }
468
            if self.pk:
469
                # ID might be None at this point, and Django 1.8 does not allow that.
470
                kwargs['master'] = self
471
472
            object = meta.model(**kwargs)
473
            local_cache[language_code] = object
474
            # Not stored in memcached here yet, first fill + save it.
475
            return object
476
477
        # 4. Fallback?
478
        fallback_msg = None
479
        lang_dict = get_language_settings(language_code)
480
481
        if language_code not in local_cache:
482
            # Explicitly set a marker for the fact that this translation uses the fallback instead.
483
            # Avoid making that query again.
484
            local_cache[language_code] = MISSING  # None value is the marker.
485
            if not self._state.adding or self.pk is not None:
486
                _cache_translation_needs_fallback(self, language_code, related_name=meta.rel_name)
487
488
        fallback_choices = [lang_dict['code']] + list(lang_dict['fallbacks'])
489
        if use_fallback and fallback_choices:
490
            # Jump to fallback language, return directly.
491
            # Don't cache under this language_code
492
            for fallback_lang in fallback_choices:
493
                if fallback_lang == language_code:  # Skip the current language, could also be fallback 1 of 2 choices
494
                    continue
495
496
                try:
497
                    return self._get_translated_model(fallback_lang, use_fallback=False, auto_create=auto_create, meta=meta)
498
                except meta.model.DoesNotExist:
499
                    pass
500
501
            fallback_msg = " (tried fallbacks {0})".format(', '.join(lang_dict['fallbacks']))
502
503
        # None of the above, bail out!
504
        raise meta.model.DoesNotExist(
505
            "{0} does not have a translation for the current language!\n"
506
            "{0} ID #{1}, language={2}{3}".format(self._meta.verbose_name, self.pk, language_code, fallback_msg or ''
507
        ))
508
509
    def _get_any_translated_model(self, meta=None):
510
        """
511
        Return any available translation.
512
        Returns None if there are no translations at all.
513
        """
514
        if meta is None:
515
            meta = self._parler_meta.root
516
517
        tr_model = meta.model
518
        local_cache = self._translations_cache[tr_model]
519
        if local_cache:
520
            # There is already a language available in the case. No need for queries.
521
            # Give consistent answers if they exist.
522
            check_languages = [self._current_language] + self.get_fallback_languages()
523
            try:
524
                for fallback_lang in check_languages:
525
                    trans = local_cache.get(fallback_lang, None)
526
                    if trans:
527
                        return trans
528
                return next(t for t in six.itervalues(local_cache) if t is not MISSING)
529
            except StopIteration:
530
                pass
531
532
        try:
533
            # Use prefetch if available, otherwise perform separate query.
534
            prefetch = self._get_prefetched_translations(meta=meta)
535
            if prefetch is not None:
536
                translation = prefetch[0]  # Already a list
537
            else:
538
                translation = self._get_translated_queryset(meta=meta)[0]
539
        except IndexError:
540
            return None
541
        else:
542
            local_cache[translation.language_code] = translation
543
            _cache_translation(translation)
544
            return translation
545
546
    def _get_translated_queryset(self, meta=None):
547
        """
548
        Return the queryset that points to the translated model.
549
        If there is a prefetch, it can be read from this queryset.
550
        """
551
        # Get via self.TRANSLATIONS_FIELD.get(..) so it also uses the prefetch/select_related cache.
552
        if meta is None:
553
            meta = self._parler_meta.root
554
555
        accessor = getattr(self, meta.rel_name)
556
        if django.VERSION >= (1, 6):
557
            # Call latest version
558
            return accessor.get_queryset()
559
        else:
560
            # Must call RelatedManager.get_query_set() and avoid calling a custom get_queryset()
561
            # method for packages with Django 1.6/1.7 compatibility.
562
            return accessor.get_query_set()
563
564
    def _get_prefetched_translations(self, meta=None):
565
        """
566
        Return the queryset with prefetch results.
567
        """
568
        if meta is None:
569
            meta = self._parler_meta.root
570
571
        related_name = meta.rel_name
572
        try:
573
            # Read the list directly, avoid QuerySet construction.
574
            # Accessing self._get_translated_queryset(parler_meta)._prefetch_done is more expensive.
575
            return self._prefetched_objects_cache[related_name]
576
        except (AttributeError, KeyError):
577
            return None
578
579
    def _read_prefetched_translations(self, meta=None):
580
        # Load the prefetched translations into the local cache.
581
        if meta is None:
582
            meta = self._parler_meta.root
583
584
        local_cache = self._translations_cache[meta.model]
585
        prefetch = self._get_prefetched_translations(meta=meta)
586
587
        languages_seen = []
588
        if prefetch is not None:
589
            for translation in prefetch:
590
                lang = translation.language_code
591
                languages_seen.append(lang)
592
                if lang not in local_cache or local_cache[lang] is MISSING:
593
                    local_cache[lang] = translation
594
595
        return languages_seen
596
597
    def save(self, *args, **kwargs):
598
        super(TranslatableModelMixin, self).save(*args, **kwargs)
599
600
        # Makes no sense to add these for translated model
601
        # Even worse: mptt 0.7 injects this parameter when it avoids updating the lft/rgt fields,
602
        # but that misses all the translated fields.
603
        kwargs.pop('update_fields', None)
604
        self.save_translations(*args, **kwargs)
605
606
    def delete(self, using=None):
607
        _delete_cached_translations(self)
608
        super(TranslatableModelMixin, self).delete(using)
609
610
    def validate_unique(self, exclude=None):
611
        """
612
        Also validate the unique_together of the translated model.
613
        """
614
        # This is called from ModelForm._post_clean() or Model.full_clean()
615
        errors = {}
616
        try:
617
            super(TranslatableModelMixin, self).validate_unique(exclude=exclude)
618
        except ValidationError as e:
619
            errors = e.message_dict  # Django 1.5 + 1.6 compatible
620
621
        for local_cache in six.itervalues(self._translations_cache):
622
            for translation in six.itervalues(local_cache):
623
                if translation is MISSING:  # Skip fallback markers
624
                    continue
625
626
                try:
627
                    translation.validate_unique(exclude=exclude)
628
                except ValidationError as e:
629
                    errors.update(e.message_dict)
630
631
        if errors:
632
            raise ValidationError(errors)
633
634
    def save_translations(self, *args, **kwargs):
635
        """
636
        The method to save all translations.
637
        This can be overwritten to implement any custom additions.
638
        This method calls :func:`save_translation` for every fetched language.
639
640
        :param args: Any custom arguments to pass to :func:`save`.
641
        :param kwargs: Any custom arguments to pass to :func:`save`.
642
        """
643
        # Copy cache, new objects (e.g. fallbacks) might be fetched if users override save_translation()
644
        # Not looping over the cache, but using _parler_meta so the translations are processed in the order of inheritance.
645
        local_caches = self._translations_cache.copy()
646
        for meta in self._parler_meta:
647
            local_cache = local_caches[meta.model]
648
            translations = list(local_cache.values())
649
650
            # Save all translated objects which were fetched.
651
            # This also supports switching languages several times, and save everything in the end.
652
            for translation in translations:
653
                if translation is MISSING:  # Skip fallback markers
654
                    continue
655
656
                self.save_translation(translation, *args, **kwargs)
657
658
    def save_translation(self, translation, *args, **kwargs):
659
        """
660
        Save the translation when it's modified, or unsaved.
661
662
        .. note::
663
664
           When a derived model provides additional translated fields,
665
           this method receives both the original and extended translation.
666
           To distinguish between both objects, check for ``translation.related_name``.
667
668
        :param translation: The translation
669
        :type translation: TranslatedFieldsModel
670
        :param args: Any custom arguments to pass to :func:`save`.
671
        :param kwargs: Any custom arguments to pass to :func:`save`.
672
        """
673
        if self.pk is None or self._state.adding:
674
            raise RuntimeError("Can't save translations when the master object is not yet saved.")
675
676
        # Translation models without any fields are also supported.
677
        # This is useful for parent objects that have inlines;
678
        # the parent object defines how many translations there are.
679
        if translation.is_modified or (translation.is_empty and not translation.pk):
680
            if not translation.master_id:  # Might not exist during first construction
681
                translation._state.db = self._state.db
682
                translation.master = self
683
            translation.save(*args, **kwargs)
684
685
    def safe_translation_getter(self, field, default=None, language_code=None, any_language=False):
686
        """
687
        Fetch a translated property, and return a default value
688
        when both the translation and fallback language are missing.
689
690
        When ``any_language=True`` is used, the function also looks
691
        into other languages to find a suitable value. This feature can be useful
692
        for "title" attributes for example, to make sure there is at least something being displayed.
693
        Also consider using ``field = TranslatedField(any_language=True)`` in the model itself,
694
        to make this behavior the default for the given field.
695
696
        .. versionchanged 1.5:: The *default* parameter may also be a callable.
697
        """
698
        meta = self._parler_meta._get_extension_by_field(field)
699
700
        # Extra feature: query a single field from a other translation.
701
        if language_code and language_code != self._current_language:
702
            try:
703
                tr_model = self._get_translated_model(language_code, meta=meta, use_fallback=True)
704
                return getattr(tr_model, field)
705
            except TranslationDoesNotExist:
706
                pass
707
        else:
708
            # By default, query via descriptor (TranslatedFieldDescriptor)
709
            # which also attempts the fallback language if configured to do so.
710
            try:
711
                return getattr(self, field)
712
            except TranslationDoesNotExist:
713
                pass
714
715
        if any_language:
716
            translation = self._get_any_translated_model(meta=meta)
717
            if translation is not None:
718
                try:
719
                    return getattr(translation, field)
720
                except KeyError:
721
                    pass
722
723
        if callable(default):
724
            return default()
725
        else:
726
            return default
727
728
729
class TranslatableModel(TranslatableModelMixin, models.Model):
730
    """
731
    Base model class to handle translations.
732
733
    All translatable fields will appear on this model, proxying the calls to the :class:`TranslatedFieldsModel`.
734
    """
735
    class Meta:
736
        abstract = True
737
738
    # change the default manager to the translation manager
739
    objects = TranslatableManager()
740
741
742
class TranslatedFieldsModelBase(ModelBase):
743
    """
744
    .. versionadded 1.2
745
746
    Meta-class for the translated fields model.
747
748
    It performs the following steps:
749
750
    * It validates the 'master' field, in case it's added manually.
751
    * It tells the original model to use this model for translations.
752
    * It adds the proxy attributes to the shared model.
753
    """
754
    def __new__(mcs, name, bases, attrs):
755
756
        # Workaround compatibility issue with six.with_metaclass() and custom Django model metaclasses:
757
        if not attrs and name == 'NewBase':
758
            if django.VERSION < (1, 5):
759
                # Let Django fully ignore the class which is inserted in between.
760
                # Django 1.5 fixed this, see https://code.djangoproject.com/ticket/19688
761
                attrs['__module__'] = 'django.utils.six'
762
                attrs['Meta'] = type(str('Meta'), (), {'abstract': True})
763
            return super(TranslatedFieldsModelBase, mcs).__new__(mcs, name, bases, attrs)
764
765
        new_class = super(TranslatedFieldsModelBase, mcs).__new__(mcs, name, bases, attrs)
766
        if bases[0] == models.Model:
767
            return new_class
768
769
        # No action in abstract models.
770
        if new_class._meta.abstract or new_class._meta.proxy:
771
            return new_class
772
773
        # Validate a manually configured class.
774
        shared_model = _validate_master(new_class)
775
776
        # Add wrappers for all translated fields to the shared models.
777
        new_class.contribute_translations(shared_model)
778
779
        return new_class
780
781
782
def _validate_master(new_class):
783
    """
784
    Check whether the 'master' field on a TranslatedFieldsModel is correctly configured.
785
    """
786
    if not new_class.master or not isinstance(new_class.master, ForwardManyToOneDescriptor):
787
        raise ImproperlyConfigured("{0}.master should be a ForeignKey to the shared table.".format(new_class.__name__))
788
789
    rel = new_class.master.field.rel
790
    shared_model = rel.to
791
792
    if not issubclass(shared_model, models.Model):
793
        # Not supporting models.ForeignKey("tablename") yet. Can't use get_model() as the models are still being constructed.
794
        raise ImproperlyConfigured("{0}.master should point to a model class, can't use named field here.".format(new_class.__name__))
795
796
    meta = shared_model._parler_meta
797
    if meta is not None:
798
        if meta._has_translations_model(new_class):
799
            raise ImproperlyConfigured("The model '{0}' already has an associated translation table!".format(shared_model.__name__))
800
        if meta._has_translations_field(rel.related_name):
801
            raise ImproperlyConfigured("The model '{0}' already has an associated translation field named '{1}'!".format(shared_model.__name__, rel.related_name))
802
803
    return shared_model
804
805
806
@python_2_unicode_compatible
807
class TranslatedFieldsModel(compat.with_metaclass(TranslatedFieldsModelBase, models.Model)):
808
    """
809
    Base class for the model that holds the translated fields.
810
    """
811
    language_code = compat.HideChoicesCharField(_("Language"), choices=settings.LANGUAGES, max_length=15, db_index=True)
812
813
    #: The mandatory Foreign key field to the shared model.
814
    master = None   # FK to shared model.
815
816
    class Meta:
817
        abstract = True
818
        if django.VERSION >= (1, 7):
819
            default_permissions = ()
820
821
    def __init__(self, *args, **kwargs):
822
        signals.pre_translation_init.send(sender=self.__class__, args=args, kwargs=kwargs)
823
        super(TranslatedFieldsModel, self).__init__(*args, **kwargs)
824
        self._original_values = self._get_field_values()
825
826
        signals.post_translation_init.send(sender=self.__class__, args=args, kwargs=kwargs)
827
828
    @property
829
    def is_modified(self):
830
        """
831
        Tell whether the object content is modified since fetching it.
832
        """
833
        return self._original_values != self._get_field_values()
834
835
    @property
836
    def is_empty(self):
837
        """
838
        True when there are no translated fields.
839
        """
840
        return len(self.get_translated_fields()) == 0
841
842
    @property
843
    def shared_model(self):
844
        """
845
        Returns the shared model this model is linked to.
846
        """
847
        return self.__class__.master.field.rel.to
848
849
    @property
850
    def related_name(self):
851
        """
852
        Returns the related name that this model is known at in the shared model.
853
        """
854
        return self.__class__.master.field.rel.related_name
855
856
    def save_base(self, raw=False, using=None, **kwargs):
857
        # As of Django 1.8, not calling translations.activate() or disabling the translation
858
        # causes get_language() to explicitly return None instead of LANGUAGE_CODE.
859
        # This helps developers find solutions by bailing out properly.
860
        #
861
        # Either use translation.activate() first, or pass the language code explicitly via
862
        # MyModel.objects.language('en').create(..)
863
        assert self.language_code is not None, ""\
864
            "No language is set or detected for this TranslatableModelMixin.\n" \
865
            "Is the translations system initialized?"
866
867
        # Send the pre_save signal
868
        using = using or router.db_for_write(self.__class__, instance=self)
869
        record_exists = self.pk is not None  # Ignoring force_insert/force_update for now.
870
        if not self._meta.auto_created:
871
            signals.pre_translation_save.send(
872
                sender=self.shared_model, instance=self,
873
                raw=raw, using=using
874
            )
875
876
        # Perform save
877
        super(TranslatedFieldsModel, self).save_base(raw=raw, using=using, **kwargs)
878
        self._original_values = self._get_field_values()
879
        _cache_translation(self)
880
881
        # Send the post_save signal
882
        if not self._meta.auto_created:
883
            signals.post_translation_save.send(
884
                sender=self.shared_model, instance=self, created=(not record_exists),
885
                raw=raw, using=using
886
            )
887
888
    def delete(self, using=None):
889
        # Send pre-delete signal
890
        using = using or router.db_for_write(self.__class__, instance=self)
891
        if not self._meta.auto_created:
892
            signals.pre_translation_delete.send(sender=self.shared_model, instance=self, using=using)
893
894
        super(TranslatedFieldsModel, self).delete(using=using)
895
        _delete_cached_translation(self)
896
897
        # Send post-delete signal
898
        if not self._meta.auto_created:
899
            signals.post_translation_delete.send(sender=self.shared_model, instance=self, using=using)
900
901
    if django.VERSION >= (1, 8):
902
903
        def _get_field_names(self):
904
            # Use the new Model._meta API.
905
            return [field.get_attname() for field in self._meta.get_fields() if not field.is_relation or field.many_to_one]
906
907
        def _get_field_values(self):
908
            # Use the new Model._meta API.
909
            return [getattr(self, field.get_attname()) for field in self._meta.get_fields() if not field.is_relation or field.many_to_one]
910
    else:
911
912
        def _get_field_names(self):
913
            # Return all field names in a consistent (sorted) manner.
914
            return [field.get_attname() for field, _ in self._meta.get_fields_with_model()]
915
916
        def _get_field_values(self):
917
            # Return all field values in a consistent (sorted) manner.
918
            return [getattr(self, field.get_attname()) for field, _ in self._meta.get_fields_with_model()]
919
920
    @classmethod
921
    def get_translated_fields(cls):
922
        return [f.name for f in cls._meta.local_fields if f.name not in ('language_code', 'master', 'id')]
923
924
    @classmethod
925
    def contribute_translations(cls, shared_model):
926
        """
927
        Add the proxy attributes to the shared model.
928
        """
929
        # Instance at previous inheritance level, if set.
930
        base = shared_model._parler_meta
931
932
        if base is not None and base[-1].shared_model is shared_model:
933
            # If a second translations model is added, register it in the same object level.
934
            base.add_meta(ParlerMeta(
935
                shared_model=shared_model,
936
                translations_model=cls,
937
                related_name=cls.master.field.rel.related_name
938
            ))
939
        else:
940
            # Place a new _parler_meta at the current inheritance level.
941
            # It links to the previous base.
942
            shared_model._parler_meta = ParlerOptions(
943
                base,
944
                shared_model=shared_model,
945
                translations_model=cls,
946
                related_name=cls.master.field.rel.related_name
947
            )
948
949
        # Assign the proxy fields
950
        for name in cls.get_translated_fields():
951
            try:
952
                # Check if an attribute already exists.
953
                # Note that the descriptor even proxies this request, so it should return our field.
954
                #
955
                # A model field might not be added yet, as this all happens in the contribute_to_class() loop.
956
                # Hence, only checking attributes here. The real fields are checked for in the _prepare() code.
957
                shared_field = getattr(shared_model, name)
958
            except AttributeError:
959
                # Add the proxy field for the shared field.
960
                TranslatedField().contribute_to_class(shared_model, name)
961
            else:
962
                # Currently not allowing to replace existing model fields with translatable fields.
963
                # That would be a nice feature addition however.
964
                if not isinstance(shared_field, (models.Field, TranslatedFieldDescriptor)):
965
                    raise TypeError("The model '{0}' already has a field named '{1}'".format(shared_model.__name__, name))
966
967
                # When the descriptor was placed on an abstract model,
968
                # it doesn't point to the real model that holds the translations_model
969
                # "Upgrade" the descriptor on the class
970
                if shared_field.field.model is not shared_model:
971
                    TranslatedField(any_language=shared_field.field.any_language).contribute_to_class(shared_model, name)
972
973
        # Make sure the DoesNotExist error can be detected als shared_model.DoesNotExist too,
974
        # and by inheriting from AttributeError it makes sure (admin) templates can handle the missing attribute.
975
        cls.DoesNotExist = type(str('DoesNotExist'), (TranslationDoesNotExist, shared_model.DoesNotExist, cls.DoesNotExist,), {})
976
977
    def __str__(self):
978
        # use format to avoid weird error in django 1.4
979
        # TypeError: coercing to Unicode: need string or buffer, __proxy__ found
980
        return "{0}".format(get_language_title(self.language_code))
981
982
    def __repr__(self):
983
        return "<{0}: #{1}, {2}, master: #{3}>".format(
984
            self.__class__.__name__, self.pk, self.language_code, self.master_id
985
        )
986
987
988
class ParlerMeta(object):
989
    """
990
    Meta data for a single inheritance level.
991
    """
992
993
    def __init__(self, shared_model, translations_model, related_name):
994
        # Store meta information of *this* level
995
        self.shared_model = shared_model
996
        self.model = translations_model
997
        self.rel_name = related_name
998
999
    def get_translated_fields(self):
1000
        """
1001
        Return the translated fields of this model.
1002
        """
1003
        # TODO: should be named get_fields() ?
1004
        # root_model always points to the real model for extensions
1005
        return self.model.get_translated_fields()
1006
1007
    def __repr__(self):
1008
        return "<ParlerMeta: {0}.{1} to {2}>".format(
1009
            self.shared_model.__name__,
1010
            self.rel_name,
1011
            self.model.__name__
1012
        )
1013
1014
1015
class ParlerOptions(object):
1016
    """
1017
    Meta data for the translatable models.
1018
    """
1019
1020
    def __init__(self, base, shared_model, translations_model, related_name):
1021
        if translations_model is None is not issubclass(translations_model, TranslatedFieldsModel):
1022
            raise TypeError("Expected a TranslatedFieldsModel")
1023
1024
        self.base = base
1025
        self.inherited = False
1026
1027
        if base is None:
1028
            # Make access easier.
1029
            self.root_model = translations_model
1030
            self.root_rel_name = related_name
1031
1032
            # Initial state for lookups
1033
            self._root = None
1034
            self._extensions = []
1035
            self._fields_to_model = OrderedDict()
1036
        else:
1037
            # Inherited situation
1038
            # Still take the base situation as starting point,
1039
            # and register the added translations as extension.
1040
            root = base._root or base
1041
            base.inherited = True
1042
            self._root = root
1043
            self.root_model = root.root_model
1044
            self.root_rel_name = root.root_rel_name
1045
1046
            # This object will amend the caches of the previous object
1047
            # The _extensions list gives access to all inheritance levels where ParlerOptions is defined.
1048
            self._extensions = list(base._extensions)
1049
            self._fields_to_model = base._fields_to_model.copy()
1050
1051
        self.add_meta(ParlerMeta(shared_model, translations_model, related_name))
1052
1053
    def add_meta(self, meta):
1054
        if self.inherited:
1055
            raise RuntimeError("Adding translations afterwards to an already inherited model is not supported yet.")
1056
1057
        self._extensions.append(meta)
1058
1059
        # Fill/amend the caches
1060
        translations_model = meta.model
1061
        for name in translations_model.get_translated_fields():
1062
            self._fields_to_model[name] = translations_model
1063
1064
    def __repr__(self):
1065
        root = self.root
1066
        return "<ParlerOptions: {0}.{1} to {2}{3}>".format(
1067
            root.shared_model.__name__,
1068
            root.rel_name,
1069
            root.model.__name__,
1070
            '' if len(self._extensions) == 1 else ", {0} extensions".format(len(self._extensions))
1071
        )
1072
1073
    @property
1074
    def root(self):
1075
        """
1076
        The top level object in the inheritance chain.
1077
        This is an alias for accessing the first item in the collection.
1078
        """
1079
        return self._extensions[0]
1080
1081
    def __iter__(self):
1082
        """
1083
        Access all :class:`ParlerMeta` objects associated.
1084
        """
1085
        return iter(self._extensions)
1086
1087
    def __getitem__(self, item):
1088
        """
1089
        Get an :class:`ParlerMeta` object by index or model.
1090
        """
1091
        try:
1092
            if isinstance(item, six.integer_types):
1093
                return self._extensions[item]
1094
            elif isinstance(item, six.string_types):
1095
                return self._get_extension_by_related_name(related_name=item)
1096
            else:
1097
                return next(meta for meta in self._extensions if meta.model == item)
1098
        except (StopIteration, IndexError, KeyError):
1099
            raise KeyError("Item '{0}' not found".format(item))
1100
1101
    def __len__(self):
1102
        return len(self._extensions)
1103
1104
    def get_all_models(self):
1105
        """
1106
        Return all translated models associated with the the shared model.
1107
        """
1108
        return [meta.model for meta in self._extensions]
1109
1110
    def get_all_fields(self):
1111
        """
1112
        Return all translated fields associated with this model.
1113
        """
1114
        return list(self._fields_to_model.keys())
1115
1116
    def get_fields_with_model(self):
1117
        """
1118
        Convenience function, return all translated fields with their model.
1119
        """
1120
        return six.iteritems(self._fields_to_model)
1121
1122
    def get_translated_fields(self, related_name=None):
1123
        """
1124
        Return the translated fields of this model.
1125
        By default, the top-level translation is required, unless ``related_name`` is provided.
1126
        """
1127
        # TODO: should be named get_fields() ?
1128
        meta = self._get_extension_by_related_name(related_name)
1129
        return meta.get_translated_fields()
1130
1131
    def get_model_by_field(self, name):
1132
        """
1133
        Find the :class:`TranslatedFieldsModel` that contains the given field.
1134
        """
1135
        try:
1136
            return self._fields_to_model[name]
1137
        except KeyError:
1138
            raise FieldError("Translated field does not exist: '{0}'".format(name))
1139
1140
    def get_model_by_related_name(self, related_name):
1141
        meta = self._get_extension_by_related_name(related_name)
1142
        return meta.model  # extensions have no base set, so root model is correct here.
1143
1144
    def _has_translations_model(self, model):
1145
        return any(meta.model == model for meta in self._extensions)
1146
1147
    def _has_translations_field(self, name):
1148
        return any(meta.rel_name == name for meta in self._extensions)
1149
1150
    def _get_extension_by_field(self, name):
1151
        """
1152
        Find the ParlerOptions object that corresponds with the given translated field.
1153
        """
1154
        if name is None:
1155
            raise TypeError("Expected field name")
1156
1157
        # Reuse existing lookups.
1158
        tr_model = self.get_model_by_field(name)
1159
        for meta in self._extensions:
1160
            if meta.model == tr_model:
1161
                return meta
1162
1163
    def _get_extension_by_related_name(self, related_name):
1164
        """
1165
        Find which model is connected to a given related name.
1166
        If the related name is ``None``, the :attr:`root_model` will be returned.
1167
        """
1168
        if related_name is None:
1169
            return self._extensions[0]
1170
1171
        for meta in self._extensions:
1172
            if meta.rel_name == related_name:
1173
                return meta
1174
1175
        raise ValueError("No translated model of '{0}' has a reverse name of '{1}'".format(
1176
            self.root.shared_model.__name__, related_name
1177
        ))
1178
1179
    def _split_fields(self, **fields):
1180
        # Split fields over their translated models.
1181
        for meta in self._extensions:
1182
            model_fields = {}
1183
            for field in meta.model.get_translated_fields():
1184
                try:
1185
                    model_fields[field] = fields[field]
1186
                except KeyError:
1187
                    pass
1188
1189
            yield (meta, model_fields)
1190