Example #1
0
class TranslatableModelForm(
        with_metaclass(TranslatableModelFormMetaclass, ModelForm)):
    def __init__(self,
                 data=None,
                 files=None,
                 auto_id='id_%s',
                 prefix=None,
                 initial=None,
                 error_class=ErrorList,
                 label_suffix=':',
                 empty_permitted=False,
                 instance=None):
        opts = self._meta
        model_opts = opts.model._meta
        object_data = {}
        language = getattr(self, 'language', get_language())
        if instance is not None:
            trans = get_cached_translation(instance)
            if not trans:
                try:
                    trans = get_translation(instance, language)
                except model_opts.translations_model.DoesNotExist:
                    trans = None
            if trans:
                object_data = model_to_dict(trans, opts.fields, opts.exclude)
        if initial is not None:
            object_data.update(initial)
        initial = object_data
        super(TranslatableModelForm,
              self).__init__(data, files, auto_id, prefix, object_data,
                             error_class, label_suffix, empty_permitted,
                             instance)

    def save(self, commit=True):
        if self.instance.pk is None:
            fail_message = 'created'
            new = True
        else:
            fail_message = 'changed'
            new = False

        if self.errors:
            opts = self.instance._meta
            raise ValueError("The %s could not be %s because the data didn't"
                             " validate." % (opts.object_name, fail_message))

        trans_model = self.instance._meta.translations_model
        language_code = self.cleaned_data.get('language_code', get_language())

        if not new:
            trans = get_cached_translation(self.instance)
            if not trans or trans.language_code != language_code:
                try:
                    trans = get_translation(self.instance, language_code)
                except trans_model.DoesNotExist:
                    trans = trans_model()
        else:
            trans = trans_model()

        trans = construct_instance(self, trans, self._meta.fields)
        trans.language_code = language_code
        trans.master = self.instance
        self.instance = combine(trans, self.Meta.model)

        super(TranslatableModelForm, self).save(commit=commit)
        return self.instance

    def _post_clean(self):
        if self.instance.pk:
            try:
                # Don't use self.instance.language_code here! That will fail if
                # the instance is not translated into the current language. If
                # it succeeded, then the instance would already be translated,
                # and there'd be no point combining it with the same
                # translation again.
                trans = get_translation(self.instance, self.language)
                trans.master = self.instance
                self.instance = combine(trans, self.Meta.model)
            except self.instance._meta.translations_model.DoesNotExist:
                self.instance = self.instance.translate(self.language)
        return super(TranslatableModelForm, self)._post_clean()
Example #2
0
 class CustomMetaclassModel(
         with_metaclass(CustomMetaclass, TranslatableModel)):
     translations = TranslatedFields()
Example #3
0
class TranslatableModel(with_metaclass(TranslatableModelBase, models.Model)):
    """
    Base model for all models supporting translated fields (via TranslatedFields).
    """
    # change the default manager to the translation manager
    objects = TranslationManager()

    class Meta:
        abstract = True

    def __init__(self, *args, **kwargs):
        tkwargs = {}  # translated fields
        skwargs = {}  # shared fields

        if 'master' in kwargs.keys():
            raise RuntimeError(
                    "Cannot init  %s class with a 'master' argument" % \
                    self.__class__.__name__
            )

        # filter out all the translated fields (including 'master' and 'language_code')
        primary_key_names = ('pk', self._meta.pk.name)
        for key in list(kwargs.keys()):
            if key in self._translated_field_names:
                if not key in primary_key_names:
                    # we exclude the pk of the shared model
                    tkwargs[key] = kwargs.pop(key)
        if not tkwargs.keys():
            # if there where no translated options, then we assume this is a
            # regular init and don't want to do any funky stuff
            super(TranslatableModel, self).__init__(*args, **kwargs)
            return

        # there was at least one of the translated fields (or a language_code)
        # in kwargs. We need to do magic.
        # extract all the shared fields (including the pk)
        for key in list(kwargs.keys()):
            if key in self._shared_field_names:
                skwargs[key] = kwargs.pop(key)
        # do the regular init minus the translated fields
        super(TranslatableModel, self).__init__(*args, **skwargs)
        # prepopulate the translations model cache with an translation model
        tkwargs['language_code'] = tkwargs.get('language_code', get_language())
        tkwargs['master'] = self
        translated = self._meta.translations_model(*args, **tkwargs)
        setattr(self, self._meta.translations_cache, translated)

    @classmethod
    def contribute_translations(cls, rel):
        """
        Contribute translations options to the inner Meta class and set the
        descriptors.
        
        This get's called from TranslatableModelBase.__new__
        """
        opts = cls._meta
        opts.translations_accessor = rel.get_accessor_name()
        opts.translations_model = rel.model
        opts.translations_cache = '%s_cache' % rel.get_accessor_name()
        trans_opts = opts.translations_model._meta

        # Set descriptors
        ignore_fields = [
            'pk',
            'master',
            opts.translations_model._meta.pk.name,
        ]
        for field in trans_opts.fields:
            if field.name in ignore_fields:
                continue
            if field.name == 'language_code':
                attr = LanguageCodeAttribute(opts)
            else:
                attr = TranslatedAttribute(opts, field.name)
            setattr(cls, field.name, attr)

    @classmethod
    def save_translations(cls, instance, **kwargs):
        """
        When this instance is saved, also save the (cached) translation
        """
        opts = cls._meta
        if hasattr(instance, opts.translations_cache):
            trans = getattr(instance, opts.translations_cache)
            if not trans.master_id:
                trans.master = instance
            trans.save()

    def translate(self, language_code):
        """
        Returns an Model instance in the specified language.
        Does NOT check if the translation already exists!
        Does NOT interact with the database.
        
        This will refresh the translations cache attribute on the instance.
        """
        tkwargs = {
            'language_code': language_code,
            'master': self,
        }
        translated = self._meta.translations_model(**tkwargs)
        setattr(self, self._meta.translations_cache, translated)
        return self

    def safe_translation_getter(self, name, default=None):
        cache = getattr(self, self._meta.translations_cache, None)
        if not cache:
            return default
        return getattr(cache, name, default)

    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]

        setattr(self, self._meta.translations_cache, translation)
        return getattr(translation, name, default)

    def get_available_languages(self):
        qs = getattr(self, self._meta.translations_accessor).all()
        if qs._result_cache is not None:
            return [obj.language_code for obj in qs]
        return qs.values_list('language_code', flat=True)

    #===========================================================================
    # Internals
    #===========================================================================

    @property
    def _shared_field_names(self):
        if getattr(self, '_shared_field_names_cache', None) is None:
            self._shared_field_names_cache = self._meta.get_all_field_names()
        return self._shared_field_names_cache

    @property
    def _translated_field_names(self):
        if getattr(self, '_translated_field_names_cache', None) is None:
            self._translated_field_names_cache = self._meta.translations_model._meta.get_all_field_names(
            )
        return self._translated_field_names_cache
Example #4
0
class TranslatableModelForm(
        with_metaclass(TranslatableModelFormMetaclass, ModelForm)):
    def __init__(self,
                 data=None,
                 files=None,
                 auto_id='id_%s',
                 prefix=None,
                 initial=None,
                 error_class=ErrorList,
                 label_suffix=':',
                 empty_permitted=False,
                 instance=None):
        opts = self._meta
        model_opts = opts.model._meta
        object_data = {}
        language = getattr(self, 'language', get_language())
        if instance is not None:
            trans = get_cached_translation(instance)
            if not trans:
                try:
                    trans = get_translation(instance, language)
                except model_opts.translations_model.DoesNotExist:
                    trans = None
            if trans:
                object_data = model_to_dict(trans, opts.fields, opts.exclude)
        if initial is not None:
            object_data.update(initial)
        initial = object_data
        super(TranslatableModelForm,
              self).__init__(data, files, auto_id, prefix, object_data,
                             error_class, label_suffix, empty_permitted,
                             instance)

    def save(self, commit=True):
        if self.instance.pk is None:
            fail_message = 'created'
            new = True
        else:
            fail_message = 'changed'
            new = False
        super(TranslatableModelForm, self).save(True)
        trans_model = self.instance._meta.translations_model
        language_code = self.cleaned_data.get('language_code', get_language())
        if not new:
            trans = get_cached_translation(self.instance)
            if not trans or trans.language_code != language_code:
                try:
                    trans = get_translation(self.instance, language_code)
                except trans_model.DoesNotExist:
                    trans = trans_model()
        else:
            trans = trans_model()

        trans.language_code = language_code
        trans.master = self.instance
        trans = save_instance(self,
                              trans,
                              self._meta.fields,
                              fail_message,
                              commit,
                              construct=True)
        return combine(trans, self.Meta.model)

    def _post_clean(self):
        if self.instance.pk:
            try:
                trans = trans = get_translation(self.instance,
                                                self.instance.language_code)
                trans.master = self.instance
                self.instance = combine(trans, self.Meta.model)
            except self.instance._meta.translations_model.DoesNotExist:
                language_code = self.cleaned_data.get('language_code',
                                                      get_language())
                self.instance = self.instance.translate(language_code)
        return super(TranslatableModelForm, self)._post_clean()
Example #5
0
class TranslatableModel(with_metaclass(TranslatableModelBase, models.Model)):
    """
    Base model for all models supporting translated fields (via TranslatedFields).
    """
    # change the default manager to the translation manager
    objects = TranslationManager()

    class Meta:
        abstract = True

    def __init__(self, *args, **kwargs):
        tkwargs = {}  # translated fields
        skwargs = {}  # shared fields

        if 'master' in kwargs.keys():
            raise RuntimeError(
                    "Cannot init  %s class with a 'master' argument" % \
                    self.__class__.__name__
            )

        # filter out all the translated fields (including 'master' and 'language_code')
        primary_key_names = ('pk', self._meta.pk.name)
        for key in list(kwargs.keys()):
            if key in self._translated_field_names:
                if not key in primary_key_names:
                    # we exclude the pk of the shared model
                    tkwargs[key] = kwargs.pop(key)
        if not tkwargs.keys():
            # if there where no translated options, then we assume this is a
            # regular init and don't want to do any funky stuff
            super(TranslatableModel, self).__init__(*args, **kwargs)
            return

        # there was at least one of the translated fields (or a language_code)
        # in kwargs. We need to do magic.
        # extract all the shared fields (including the pk)
        for key in list(kwargs.keys()):
            if key in self._shared_field_names:
                skwargs[key] = kwargs.pop(key)
        # do the regular init minus the translated fields
        super(TranslatableModel, self).__init__(*args, **skwargs)
        # prepopulate the translations model cache with an translation model
        tkwargs['language_code'] = tkwargs.get('language_code', get_language())
        tkwargs['master'] = self
        translated = self._meta.translations_model(*args, **tkwargs)
        setattr(self, self._meta.translations_cache, translated)

    @classmethod
    def contribute_translations(cls, rel):
        """
        Contribute translations options to the inner Meta class and set the
        descriptors.
        
        This get's called from TranslatableModelBase.__new__
        """
        opts = cls._meta
        opts.translations_accessor = rel.get_accessor_name()
        opts.translations_model = rel.model
        opts.translations_cache = '%s_cache' % rel.get_accessor_name()
        trans_opts = opts.translations_model._meta

        # Set descriptors
        ignore_fields = [
            'pk',
            'master',
            opts.translations_model._meta.pk.name,
        ]
        for field in trans_opts.fields:
            if field.name in ignore_fields:
                continue
            if field.name == 'language_code':
                attr = LanguageCodeAttribute(opts)
            else:
                attr = TranslatedAttribute(opts, field.name)
            setattr(cls, field.name, attr)

    @classmethod
    def save_translations(cls, instance, **kwargs):
        """
        When this instance is saved, also save the (cached) translation
        """
        opts = cls._meta
        if hasattr(instance, opts.translations_cache):
            trans = getattr(instance, opts.translations_cache)
            if not trans.master_id:
                trans.master = instance
            trans.save()

    def translate(self, language_code):
        """
        Returns an Model instance in the specified language.
        Does NOT check if the translation already exists!
        Does NOT interact with the database.
        
        This will refresh the translations cache attribute on the instance.
        """
        tkwargs = {
            'language_code': language_code,
            'master': self,
        }
        translated = self._meta.translations_model(**tkwargs)
        setattr(self, self._meta.translations_cache, translated)
        return self

    def safe_translation_getter(self, name, default=None):
        cache = getattr(self, self._meta.translations_cache, None)
        if not cache:
            return default
        return getattr(cache, name, default)

    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 = self._meta.translations_model.objects.filter(
            master__pk=self.pk)

        # make them a nice dict
        translation_dict = {}
        for translation in translations:
            translation_dict[translation.language_code] = translation

        # see if we have the right language
        correct_language = translation_dict.get(get_language())
        if correct_language:
            # CACHE IT!
            setattr(self, self._meta.translations_cache, correct_language)
            return getattr(correct_language, name, default)

        # Nope? Ok, let's try the default language for the installation
        default_language = translation_dict.get(settings.LANGUAGE_CODE)
        if default_language:
            # CACHE IT!
            setattr(self, self._meta.translations_cache, default_language)
            return getattr(default_language, name, default)

        # *Sigh*, OK, any available langauge?
        try:
            any_language = list(translation_dict.values())[0]
        except IndexError:
            # OK, can't say we didn't try!
            return default

        setattr(self, self._meta.translations_cache, any_language)
        return getattr(any_language, name, default)

    def get_available_languages(self):
        manager = self._meta.translations_model.objects
        return manager.filter(master=self).values_list('language_code',
                                                       flat=True)

    #===========================================================================
    # Internals
    #===========================================================================

    @property
    def _shared_field_names(self):
        if getattr(self, '_shared_field_names_cache', None) is None:
            self._shared_field_names_cache = self._meta.get_all_field_names()
        return self._shared_field_names_cache

    @property
    def _translated_field_names(self):
        if getattr(self, '_translated_field_names_cache', None) is None:
            self._translated_field_names_cache = self._meta.translations_model._meta.get_all_field_names(
            )
        return self._translated_field_names_cache