Esempio n. 1
0
    def clean(self):
        super(BaseTranslationFormSet, self).clean()

        # Trigger combined instance validation
        master = self.instance
        stashed = get_cached_translation(master)

        for form in self.forms:
            set_cached_translation(master, form.instance)
            exclusions = form._get_validation_exclusions()
            # fields from the shared model should not be validated
            exclusions.extend(f.name for f in master._meta.fields)
            try:
                master.clean()
            except ValidationError as e:
                form._update_errors(e)

        set_cached_translation(master, stashed)

        # Validate that at least one translation exists
        forms_to_delete = self.deleted_forms
        provided = [
            form for form in self.forms
            if (getattr(form.instance, 'pk', None) is not None
                or form.has_changed()) and not form in forms_to_delete
        ]
        if len(provided) < 1:
            raise ValidationError(
                _('At least one translation must be provided'),
                code='notranslation')
Esempio n. 2
0
    def lazy_translation_getter(self, name, default=None):
        """
        Lazy translation getter that fetches translations from DB in case the instance is currently untranslated and
        saves the translation instance in the translation cache
        """
        stuff = self.safe_translation_getter(name, NoTranslation)
        if stuff is not NoTranslation:
            return stuff

        # get all translations
        translations = getattr(self, self._meta.translations_accessor).all()

        # if no translation exists, bail out now
        if len(translations) == 0:
            return default

        # organize translations into a nice dict
        translation_dict = dict((t.language_code, t) for t in translations)

        # see if we have the right language, or any language in fallbacks
        for code in (get_language(), settings.LANGUAGE_CODE) + FALLBACK_LANGUAGES:
            try:
                translation = translation_dict[code]
            except KeyError:
                continue
            break
        else:
            # none of the fallbacks was found, pick an arbitrary translation
            translation = translation_dict.popitem()[1]

        set_cached_translation(self, translation)
        return getattr(translation, name, default)
Esempio n. 3
0
    def clean(self):
        super(BaseTranslationFormSet, self).clean()

        # Trigger combined instance validation
        master = self.instance
        stashed = get_cached_translation(master)

        for form in self.forms:
            set_cached_translation(master, form.instance)
            exclusions = form._get_validation_exclusions()
            # fields from the shared model should not be validated
            exclusions.extend(f.name for f in master._meta.fields)
            try:
                master.clean()
            except ValidationError as e:
                form._update_errors(e)

        set_cached_translation(master, stashed)

        # Validate that at least one translation exists
        forms_to_delete = self.deleted_forms
        provided = [form for form in self.forms
                    if (getattr(form.instance, 'pk', None) is not None or
                        form.has_changed())
                       and not form in forms_to_delete]
        if len(provided) < 1:
            raise ValidationError(_('At least one translation must be provided'),
                                  code='notranslation')
Esempio n. 4
0
    def save(self, commit=True):
        ''' Saves the model
            If will always use the language specified in self.cleaned_data, with
            the usual None meaning 'call get_language()'. If instance has
            another language loaded, it gets reloaded with the new language.

            If no language is specified in self.cleaned_data, assume the instance
            is preloaded with correct language.
        '''
        assert self.is_valid(), (
            'Method save() must not be called on an invalid '
            'form. Check the result of .is_valid() before '
            'calling save().')

        # Get the right translation for object and language
        # It should have been done in _post_clean, but instance may have been
        # changed since.
        enforce = 'language_code' in self.cleaned_data
        language = self.cleaned_data.get('language_code') or get_language()
        translation = load_translation(self.instance, language, enforce)

        # Fill the translated fields with values from the form
        excludes = list(self._meta.exclude) + ['master', 'language_code']
        translation = construct_instance(self, translation, self._meta.fields,
                                         excludes)
        set_cached_translation(self.instance, translation)

        # Delegate shared fields to super()
        return super(BaseTranslatableModelForm, self).save(commit=commit)
Esempio n. 5
0
 def translate(self, language_code):
     ''' Create a new translation for current instance.
         Does NOT check if the translation already exists!
     '''
     set_cached_translation(
         self, self._meta.translations_model(language_code=language_code))
     return self
Esempio n. 6
0
    def update(self, instance, data):
        accessor = self.Meta.model._meta.translations_accessor
        translations_data = data.pop(accessor, None)
        if translations_data:
            arbitrary = translations_data.popitem()
            data.update(arbitrary[1])
            stashed = set_cached_translation(
                instance, load_translation(instance,
                                           arbitrary[0],
                                           enforce=True))
            instance = super(TranslationsMixin, self).update(instance, data)

            for language, translation_data in translations_data.items():
                set_cached_translation(
                    instance, load_translation(instance,
                                               language,
                                               enforce=True))
                self.update_translation(instance, translation_data)

            set_cached_translation(instance, stashed)
            qs = instance._meta.translations_model.objects
            (qs.filter(master=instance).exclude(
                language_code__in=(arbitrary[0], ) +
                tuple(translations_data.keys())).delete())
        else:
            instance = super(TranslationsMixin, self).update(instance, data)
        return instance
Esempio n. 7
0
    def lazy_translation_getter(self, name, default=None):
        """
        Lazy translation getter that fetches translations from DB in case the instance is currently untranslated and
        saves the translation instance in the translation cache
        """
        stuff = self.safe_translation_getter(name, NoTranslation)
        if stuff is not NoTranslation:
            return stuff

        # get all translations
        translations = getattr(self, self._meta.translations_accessor).all()

        # if no translation exists, bail out now
        if len(translations) == 0:
            return default

        # organize translations into a nice dict
        translation_dict = dict((t.language_code, t) for t in translations)

        # see if we have the right language, or any language in fallbacks
        for code in (get_language(),
                     settings.LANGUAGE_CODE) + FALLBACK_LANGUAGES:
            try:
                translation = translation_dict[code]
            except KeyError:
                continue
            break
        else:
            # none of the fallbacks was found, pick an arbitrary translation
            translation = translation_dict.popitem()[1]

        set_cached_translation(self, translation)
        return getattr(translation, name, default)
Esempio n. 8
0
    def save(self, commit=True):
        ''' Saves the model
            If will always use the language specified in self.cleaned_data, with
            the usual None meaning 'call get_language()'. If instance has
            another language loaded, it gets reloaded with the new language.

            If no language is specified in self.cleaned_data, assume the instance
            is preloaded with correct language.
        '''
        assert self.is_valid(), ('Method save() must not be called on an invalid '
                                 'form. Check the result of .is_valid() before '
                                 'calling save().')

        # Get the right translation for object and language
        # It should have been done in _post_clean, but instance may have been
        # changed since.
        enforce = 'language_code' in self.cleaned_data
        if getattr(self, 'is_edit', False):
            language = self.language or get_language()
        else:
            language = self.cleaned_data.get('language_code') or get_language()
        translation = load_translation(self.instance, language, enforce)

        # Fill the translated fields with values from the form
        excludes = list(self._meta.exclude) + ['master', 'language_code']
        translation = construct_instance(self, translation,
                                         self._meta.fields, excludes)
        if getattr(self, 'is_edit', False):
            translation.language_code = \
                self.cleaned_data.get('language_code', None) or self.new_lang
        set_cached_translation(self.instance, translation)

        # Delegate shared fields to super()
        return super(BaseTranslatableModelForm, self).save(commit=commit)
Esempio n. 9
0
    def update(self, instance, data):
        'Handle switching to correct translation before actual update'
        enforce = 'language_code' in data
        language = data.pop('language_code', None) or get_language()
        translation = load_translation(instance, language, enforce)
        set_cached_translation(instance, translation)

        return super(TranslatableModelMixin, self).update(instance, data)
Esempio n. 10
0
    def update(self, instance, data):
        'Handle switching to correct translation before actual update'
        enforce = 'language_code' in data
        language = data.pop('language_code', None) or get_language()
        translation = load_translation(instance, language, enforce)
        set_cached_translation(instance, translation)

        return super(TranslatableModelMixin, self).update(instance, data)
Esempio n. 11
0
 def to_representation(self, instance):
     ''' Combine each translation in turn so the serializer has a full object '''
     result = {}
     stashed = get_cached_translation(instance)
     for translation in getattr(instance, self.source).all():
         set_cached_translation(instance, translation)
         result[translation.language_code] = self.child.to_representation(instance)
     set_cached_translation(instance, stashed)
     return result
Esempio n. 12
0
    def to_representation(self, instance):
        'Switch language if we are in enforce mode'
        enforce = hasattr(self, 'language')
        language = getattr(self, 'language', None) or get_language()

        translation = load_translation(instance, language, enforce)
        set_cached_translation(instance, translation)

        return super(TranslatableModelMixin, self).to_representation(instance)
Esempio n. 13
0
    def to_representation(self, instance):
        'Switch language if we are in enforce mode'
        enforce = hasattr(self, 'language')
        language = getattr(self, 'language', None) or get_language()

        translation = load_translation(instance, language, enforce)
        set_cached_translation(instance, translation)

        return super(TranslatableModelMixin, self).to_representation(instance)
Esempio n. 14
0
 def translate(self, language_code):
     ''' Create a new translation for current instance.
         Does NOT check if the translation already exists!
     '''
     set_cached_translation(
         self,
         self._meta.translations_model(language_code=language_code)
     )
     return self
Esempio n. 15
0
 def translation(self, instance):
     translation = get_cached_translation(instance)
     if translation is None:
         try:
             translation = get_translation(instance)
         except self.opts.translations_model.DoesNotExist:
             language_code = instance.default_language
             translation = instance.translations.get(language_code = language_code)
         set_cached_translation(instance, translation)
     return translation
Esempio n. 16
0
 def to_representation(self, instance):
     ''' Combine each translation in turn so the serializer has a full object '''
     result = {}
     stashed = get_cached_translation(instance)
     for translation in getattr(instance, self.source).all():
         set_cached_translation(instance, translation)
         result[translation.language_code] = self.child.to_representation(
             instance)
     set_cached_translation(instance, stashed)
     return result
Esempio n. 17
0
 def translation(self, instance):
     translation = get_cached_translation(instance)
     if translation is None:
         try:
             translation = get_translation(instance)
         except self.opts.translations_model.DoesNotExist:
             raise self._NoTranslationError('Accessing a translated field requires that '
                                            'the instance has a translation loaded, or a '
                                            'valid translation in current language (%s) '
                                            'loadable from the database' % get_language())
         set_cached_translation(instance, translation)
     return translation
Esempio n. 18
0
 def translation(self, instance):
     translation = get_cached_translation(instance)
     if translation is None:
         try:
             translation = get_translation(instance)
         except self.opts.translations_model.DoesNotExist:
             raise self._NoTranslationError('Accessing a translated field requires that '
                                            'the instance has a translation loaded, or a '
                                            'valid translation in current language (%s) '
                                            'loadable from the database' % get_language())
         set_cached_translation(instance, translation)
     return translation
Esempio n. 19
0
    def _save_translation(self, form, commit=True):
        obj = form.save(commit=False)
        assert isinstance(obj, BaseTranslationModel)

        if commit:
            # We need to trigger custom save actions on the combined model
            stashed = set_cached_translation(self.instance, obj)
            self.instance.save()
            if hasattr(obj, 'save_m2m'):  # pragma: no cover
                # cannot happen, but feature could be added, be ready
                obj.save_m2m()
            set_cached_translation(self.instance, stashed)
        return obj
Esempio n. 20
0
    def _save_translation(self, form, commit=True):
        obj = form.save(commit=False)
        assert isinstance(obj, BaseTranslationModel)

        if commit:
            # We need to trigger custom save actions on the combined model
            stashed = set_cached_translation(self.instance, obj)
            self.instance.save()
            if hasattr(obj, 'save_m2m'): # pragma: no cover
                # cannot happen, but feature could be added, be ready
                obj.save_m2m()
            set_cached_translation(self.instance, stashed)
        return obj
Esempio n. 21
0
    def _post_clean(self):
        ''' Switch the translation on self.instance
            This cannot (and should not) be done in clean() because it could be
            overriden to change the language. Yet it should be done before save()
            to allow an overriden save to set some translated field values before
            invoking super().
        '''
        result = super(BaseTranslatableModelForm, self)._post_clean()

        enforce = 'language_code' in self.cleaned_data
        language = self.cleaned_data.get('language_code') or get_language()
        translation = load_translation(self.instance, language, enforce)
        set_cached_translation(self.instance, translation)
        return result
Esempio n. 22
0
 def load_translation(self, instance):
     if not hvad_settings.AUTOLOAD_TRANSLATIONS:
         raise AttributeError('Field %r is a translatable field, but no translation is loaded '
                              'and auto-loading is disabled because '
                              'settings.HVAD[\'AUTOLOAD_TRANSLATIONS\'] is False' % self.name)
     try:
         translation = get_translation(instance)
     except instance._meta.translations_model.DoesNotExist:
         raise self._NoTranslationError('Accessing a translated field requires that '
                                        'the instance has a translation loaded, or a '
                                        'valid translation in current language (%s) '
                                        'loadable from the database' % get_language())
     set_cached_translation(instance, translation)
     return translation
Esempio n. 23
0
    def _post_clean(self):
        ''' Switch the translation on self.instance
            This cannot (and should not) be done in clean() because it could be
            overriden to change the language. Yet it should be done before save()
            to allow an overriden save to set some translated field values before
            invoking super().
        '''
        result = super(BaseTranslatableModelForm, self)._post_clean()

        enforce = 'language_code' in self.cleaned_data
        language = self.cleaned_data.get('language_code') or get_language()
        translation = load_translation(self.instance, language, enforce)
        set_cached_translation(self.instance, translation)
        return result
Esempio n. 24
0
    def __init__(self, *args, **kwargs):
        # Split arguments into shared/translatd
        veto_names = ('pk', 'master', 'master_id', self._meta.translations_model._meta.pk.name)
        skwargs, tkwargs = {}, {}
        for key, value in kwargs.items():
            if key in self._translated_field_names and not key in veto_names:
                tkwargs[key] = value
            else:
                skwargs[key] = value

        super(TranslatableModel, self).__init__(*args, **skwargs)

        # Create a translation if there are translated fields
        if tkwargs:
            tkwargs['language_code'] = tkwargs.get('language_code') or get_language()
            set_cached_translation(self, self._meta.translations_model(**tkwargs))
Esempio n. 25
0
    def __init__(self, *args, **kwargs):
        # Split arguments into shared/translatd
        veto_names = ('pk', 'master', 'master_id', self._meta.translations_model._meta.pk.name)
        skwargs, tkwargs = {}, {}
        for key, value in kwargs.items():
            if key in self._translated_field_names and not key in veto_names:
                tkwargs[key] = value
            else:
                skwargs[key] = value

        super(TranslatableModel, self).__init__(*args, **skwargs)

        # Create a translation if there are translated fields
        if tkwargs:
            tkwargs['language_code'] = tkwargs.get('language_code') or get_language()
            set_cached_translation(self, self._meta.translations_model(**tkwargs))
Esempio n. 26
0
    def _save_translation(self, form, commit=True):
        """ Save translation for given translation form.
            Do it by loading it onto the master object and saving the master object
            so custom save() behavior is properly triggered.
        """
        obj = form.save(commit=False)
        assert isinstance(obj, BaseTranslationModel)

        if commit:
            # We need to trigger custom save actions on the combined model
            stashed = set_cached_translation(self.instance, obj)
            self.instance.save()
            if hasattr(obj, 'save_m2m'): # pragma: no cover
                # cannot happen, but feature could be added, be ready
                obj.save_m2m()
            set_cached_translation(self.instance, stashed)
        return obj
Esempio n. 27
0
 def __init__(self, *args, **kwargs):
     # Split arguments into shared/translatd
     veto_names = ('pk', 'master', 'master_id', self._meta.translations_model._meta.pk.name)
     skwargs, tkwargs = {}, {}
     translations_opts = self._meta.translations_model._meta
     for key, value in kwargs.items():
         if key in veto_names:
             skwargs[key] = value
         else:
             try:
                 translations_opts.get_field(key)
             except FieldDoesNotExist:
                 skwargs[key] = value
             else:
                 tkwargs[key] = value
     super(TranslatableModel, self).__init__(*args, **skwargs)
     if tkwargs:
         tkwargs['language_code'] = tkwargs.get('language_code') or get_language()
         set_cached_translation(self, self._meta.translations_model(**tkwargs))
Esempio n. 28
0
    def _post_clean(self):
        ''' Switch the translation on self.instance
            This cannot (and should not) be done in clean() because it could be
            overriden to change the language. Yet it should be done before save()
            to allow an overriden save to set some translated field values before
            invoking super().
        '''

        enforce = 'language_code' in self.cleaned_data
        if getattr(self, 'is_edit', False):
            language = self.language or get_language()
            result = super(BaseTranslatableModelForm, self)._post_clean()
        else:
            language = self.cleaned_data.get('language_code') or get_language()
        translation = load_translation(self.instance, language, enforce)
        exclude = self._get_validation_exclusions()
        translation = construct_instance(self, translation, self._meta.fields, exclude)
        set_cached_translation(self.instance, translation)
        if not getattr(self, 'is_edit', False):
            result = super(BaseTranslatableModelForm, self)._post_clean()
        return result
Esempio n. 29
0
 def __init__(self, *args, **kwargs):
     # Split arguments into shared/translatd
     veto_names = ('pk', 'master', 'master_id',
                   self._meta.translations_model._meta.pk.name)
     skwargs, tkwargs = {}, {}
     translations_opts = self._meta.translations_model._meta
     for key, value in kwargs.items():
         if key in veto_names:
             skwargs[key] = value
         else:
             try:
                 translations_opts.get_field(key)
             except FieldDoesNotExist:
                 skwargs[key] = value
             else:
                 tkwargs[key] = value
     super(TranslatableModel, self).__init__(*args, **skwargs)
     if tkwargs:
         tkwargs['language_code'] = tkwargs.get(
             'language_code') or get_language()
         set_cached_translation(self,
                                self._meta.translations_model(**tkwargs))
Esempio n. 30
0
    def save(self, commit=True):
        ''' Saves the model
            If will always use the language specified in self.cleaned_data, with
            the usual None meaning 'call get_language()'. If instance has
            another language loaded, it gets reloaded with the new language.

            If no language is specified in self.cleaned_data, assume the instance
            is preloaded with correct language.
        '''
        if not self.is_valid():
            # raise in 1.3, remove in 1.5
            warnings.warn(
                'Calling save() on an invalid form is deprecated and '
                'will fail in the future. Check the result of .is_valid() '
                'before calling save().',
                DeprecationWarning,
                stacklevel=2)
            raise ValueError((_(
                "The %s could not be created because the data didn't validate."
            ) if self.instance.pk is None else _(
                "The %s could not be changed because the data didn't validate."
            )) % self.instance._meta.object_name)

        # Get the right translation for object and language
        # It should have been done in _post_clean, but instance may have been
        # changed since.
        enforce = 'language_code' in self.cleaned_data
        language = self.cleaned_data.get('language_code') or get_language()
        translation = load_translation(self.instance, language, enforce)

        # Fill the translated fields with values from the form
        excludes = list(self._meta.exclude) + ['master', 'language_code']
        translation = construct_instance(self, translation, self._meta.fields,
                                         excludes)
        set_cached_translation(self.instance, translation)

        # Delegate shared fields to super()
        return super(BaseTranslatableModelForm, self).save(commit=commit)
Esempio n. 31
0
    def test_get_cached_translation(self):
        obj = Normal.objects.untranslated().get(pk=self.normal_id[1])
        with self.assertNumQueries(0):
            self.assertIs(get_cached_translation(obj), None)
            self.assertFalse(obj._meta.get_field('_hvad_query').is_cached(obj))

            obj.translate('sr')
            self.assertIsNot(get_cached_translation(obj), None)
            self.assertEqual(get_cached_translation(obj).language_code, 'sr')

        old = set_cached_translation(obj, obj.translations.get_language('ja'))
        with self.assertNumQueries(0):
            self.assertEqual(old.language_code, 'sr')
            self.assertEqual(obj.language_code, 'ja')
            self.assertEqual(get_cached_translation(obj).language_code, 'ja')

            set_cached_translation(obj, None)
            self.assertIs(get_cached_translation(obj), None)
            self.assertFalse(obj._meta.get_field('_hvad_query').is_cached(obj))

        obj = Normal.objects.language('en').get(pk=self.normal_id[1])
        with self.assertNumQueries(0):
            self.assertEqual(get_cached_translation(obj).language_code, 'en')
Esempio n. 32
0
    def update(self, instance, data):
        accessor = self.Meta.model._meta.translations_accessor
        translations_data = data.pop(accessor, None)
        if translations_data:
            arbitrary = translations_data.popitem()
            data.update(arbitrary[1])
            stashed = set_cached_translation(
                instance, load_translation(instance, arbitrary[0], enforce=True)
            )
            instance = super(TranslationsMixin, self).update(instance, data)

            for language, translation_data in translations_data.items():
                set_cached_translation(instance, load_translation(instance, language, enforce=True))
                self.update_translation(instance, translation_data)

            set_cached_translation(instance, stashed)
            qs = instance._meta.translations_model.objects
            (qs.filter(master=instance)
                .exclude(language_code__in=(arbitrary[0],)+tuple(translations_data.keys()))
                .delete())
        else:
            instance = super(TranslationsMixin, self).update(instance, data)
        return instance
Esempio n. 33
0
    def test_get_cached_translation(self):
        obj = Normal.objects.untranslated().get(pk=self.normal_id[1])
        with self.assertNumQueries(0):
            self.assertIs(get_cached_translation(obj), None)
            self.assertFalse(hasattr(obj, obj._meta.translations_cache))

            obj.translate('sr')
            self.assertIsNot(get_cached_translation(obj), None)
            self.assertEqual(get_cached_translation(obj).language_code, 'sr')

        old = set_cached_translation(obj, obj.translations.get_language('ja'))
        with self.assertNumQueries(0):
            self.assertEqual(old.language_code, 'sr')
            self.assertEqual(obj.language_code, 'ja')
            self.assertEqual(get_cached_translation(obj).language_code, 'ja')

            set_cached_translation(obj, None)
            self.assertIs(get_cached_translation(obj), None)
            self.assertFalse(hasattr(obj, obj._meta.translations_cache))

        obj = Normal.objects.language('en').get(pk=self.normal_id[1])
        with self.assertNumQueries(0):
            self.assertEqual(get_cached_translation(obj).language_code, 'en')
Esempio n. 34
0
 def activate(self, language):
     """ Make translation in specified language current for the instance
         - Only available from shared model translations accessor
         - Load all translations if they were not already loaded
         - Passing None unloads current translation
         - Raise a DoesNotExist exception if no translation exist for that language
     """
     if language is None:
         translation = None
     elif language.__class__ is self.model:
         if language.master_id is not None and language.master_id != self.instance.pk:
             raise ValueError(
                 'Trying to activate a translation that does not '
                 'belong to this %s' %
                 (self.instance.__class__.__name__, ))
         translation = language
     else:
         self.prefetch()
         try:
             translation = next(obj for obj in self.all()
                                if obj.language_code == language)
         except StopIteration:
             raise self.model.DoesNotExist
     set_cached_translation(self.instance, translation)
Esempio n. 35
0
    def save(self, commit=True):
        ''' Saves the model
            If will always use the language specified in self.cleaned_data, with
            the usual None meaning 'call get_language()'. If instance has
            another language loaded, it gets reloaded with the new language.

            If no language is specified in self.cleaned_data, assume the instance
            is preloaded with correct language.
        '''
        if not self.is_valid():
            # raise in 1.3, remove in 1.5
            warnings.warn('Calling save() on an invalid form is deprecated and '
                          'will fail in the future. Check the result of .is_valid() '
                          'before calling save().', DeprecationWarning, stacklevel=2)
            raise ValueError((
                _("The %s could not be created because the data didn't validate.")
                if self.instance.pk is None else
                _("The %s could not be changed because the data didn't validate.")
                ) % self.instance._meta.object_name
            )

        # Get the right translation for object and language
        # It should have been done in _post_clean, but instance may have been
        # changed since.
        enforce = 'language_code' in self.cleaned_data
        language = self.cleaned_data.get('language_code') or get_language()
        translation = load_translation(self.instance, language, enforce)

        # Fill the translated fields with values from the form
        excludes = list(self._meta.exclude) + ['master', 'language_code']
        translation = construct_instance(self, translation,
                                         self._meta.fields, excludes)
        set_cached_translation(self.instance, translation)

        # Delegate shared fields to super()
        return super(BaseTranslatableModelForm, self).save(commit=commit)