1. Kostas Papadimitriou
  2. django-pluggable-model-i18n

Commits

mati...@fd93c002-e6a4-11dd-a971-7dbc132099af  committed a48cbe4

Changes:
* Implement managers/queryset get_translations()
* Added master model switch_language() method to
switch between loaded languages and master too
Refs #5

  • Participants
  • Parent commits 01f58e9
  • Branches default

Comments (0)

Files changed (3)

File model_i18n/conf.py

View file
 # Master model reverse relation related name
 RELATED_NAME = 'translations'
 
+# Current loaded languages attribute name
+CURRENT_LANGUAGES = 'current_languages'
+
 # Do we have multidb support? (post r11952)
 try:
     from django.db import DEFAULT_DB_ALIAS

File model_i18n/query.py

View file
+import operator
+
 from django.db import connection
 from django.db.models.sql import Query
 from django.db.models.query_utils import Q
 from django.db.models.sql.where import AND
 from django.db.models.query import QuerySet
-from model_i18n.conf import ATTR_BACKUP_SUFFIX 
+
+from model_i18n.conf import ATTR_BACKUP_SUFFIX, CURRENT_LANGUAGES
 from model_i18n.utils import get_master_language
 
 
                     for alias, (table, where) in self.joins.iteritems()
                         if alias not in used_aliases ]
 
+    def __and__(self, right):
+        if not isinstance(right, QOuterJoins):
+            return super(QOuterJoins, self).__and__(right)
+        self.joins.update(right.joins)
+        return self
+
 
 class TransJoin(QOuterJoins):
     """Q Object which joins translation table and retrieves translatable
             lang: language desired
         """
         self.model = model
-        self.lang = lang
-        self.alias = 'translation_%s' % lang
 
         trans_model = model._translation_model
         trans_opts = trans_model._transmeta
 
-        # Translatable fields
-        self.fields = trans_opts.translatable_fields
+        alias = 'translation_%s' % lang
+        self.data = { alias: lang }
 
         # Join data
         related_col  = trans_opts.master_field_name
                     'm_table': QN(master_table),
                     'm_pk':  QN(master_pk),
                     'and': AND,
-                    'alias': self.alias,
+                    'alias': alias,
                     't_fk': QN(trans_fk),
                     't_lang': QN(trans_opts.language_field_name),
                     'lang': lang }
-        super(TransJoin, self).__init__(**{ self.alias: (trans_table, where) })
+        super(TransJoin, self).__init__(**{ alias: (trans_table, where) })
 
     def add_to_query(self, query, used_aliases):
         """
         Delegates join to QOuterJoins and adds the needed fields to 
         select list. The translateable fields will be in the form:
             <master model attribute name>_<language code>.
-        Also current_language attribute will be added with translation
-        language code.
+        Also current_languages attribute will be added with translation
+        language codes joined by '_'.
         """
         # resolve joins
         super(TransJoin, self).add_to_query(query, used_aliases)
+        fields = self.model._translation_model._transmeta.translatable_fields
 
         # add joined columns needed
-        q_trans_table = QN(self.alias)
-        select = dict(('%s_%s' % (name, self.lang),
-                       '%s.%s' % (q_trans_table, QN(name)))
-                            for name in self.fields)
-        select['current_language'] = '"%s"' % self.lang
+        select = {}
+        for alias, lang in self.data.iteritems():
+            alias = QN(alias)
+            select.update(('%s_%s' % (name, lang),
+                           '%s.%s' % (alias, QN(name)))
+                                for name in fields)
+        select[CURRENT_LANGUAGES] = '"%s"' % '_'.join(self.data.itervalues())
         query.add_extra(select, None, None, None, None, None)
 
+    def __and__(self, right):
+        if isinstance(right, TransJoin) and self.model == right.model:
+            self.data.update(right.data)
+        return super(TransJoin, self).__and__(right)
+
 
 class TransQuerySet(QuerySet):
     """ Translation QuerySet class
     QuerySet that joins with translation table, retrieves translated
     values and setup model attributes
     """
-    def set_language(self, language_code):
-        """ Sets current query set language, if language requested
-        is the same as model master language no translation is needed
-        at all since master language is stored in main model db table,
-        so we avoid the translation join
+    def __init__(self, *args, **kwargs):
+        self.languages = set()
+        self.lang = None
+        super(TransQuerySet, self).__init__(*args, **kwargs)
+
+    def set_language(self, language):
+        """ Defines/switch query set implicit language, attributes on
+        result instances will be switched to this language on change_fields
         """
-        return self.filter(TransJoin(self.model, language_code)) \
-                if language_code != get_master_language(self.model) \
-                    else self
+        return self.get_translations([language], language)
+
+    def get_translations(self, languages, language=None):
+        """ Adds any non-master new languages in parameter to requested
+        languages list (self.languages) and build the new query joins rules
+
+        We do not do anything if no new languages were passed
+
+        `language` parameter will be set as implicit language (self.lang)
+                   if passed
+        """
+        if language and language not in languages:
+            languages.append(language)
+
+        # filter added languages and master language
+        master = get_master_language(self.model)
+        new = set((lang for lang in languages
+                        if lang and lang != master)) - self.languages
+
+        if language not in (self.lang, master): # set implicit language
+            self.lang = language
+
+        if new: # if there's any language to add
+            rules = [ TransJoin(self.model, lang) for lang in new ]
+            join = reduce(operator.and_, rules) if len(rules) > 1 else rules[0]
+            self.languages |= new
+            return self.filter(join)
+        return self
 
     def iterator(self):
         """ Invokes QuerySet iterator method and tries to change instance
             yield self.change_fields(obj)
 
     def change_fields(self, instance):
-        """Here we overrides the default fields with their translated
-        values.  If there's no value in the translated field, we keep
-        the default.
+        """Here we backups master values in <name>_<ATTR_BACKUP_SUFFIX>
+        and overrides the default fields with their translated values using
+        instance set_language.
         """
-        if hasattr(instance, 'current_language'):
-            lang = instance.current_language
-            trans_opts = instance._translation_model._transmeta
+        trans_opts = instance._translation_model._transmeta
 
-            # if there's no translation, then don't subtitute anything
-            if lang != trans_opts.master_language:
-                for name in trans_opts.translatable_fields:
-                    value = getattr(instance, '_'.join((name, lang)), None)
-                    # None values (meaning not translated) are left untouched
-                    if value is not None:
-                        original = getattr(instance, name, None)
-                        # backup value on <name>_<suffix> attribute
-                        backup_name = '_'.join((name, ATTR_BACKUP_SUFFIX))
-                        setattr(instance, backup_name, original)
-                        setattr(instance, name, value)
+        # backup master value on <name>_<suffix> attribute
+        apply(lambda name: setattr(instance,
+                                   '_'.join((name, ATTR_BACKUP_SUFFIX)),
+                                   getattr(instance, name, None)),
+               trans_opts.translatable_fields)
+
+        languages = filter(None, getattr(instance,
+                                         CURRENT_LANGUAGES, '').split('_'))
+        implicit = self.lang
+        if implicit and implicit in languages:
+            instance.set_language(implicit) # switch to implicit language
+        setattr(instance, CURRENT_LANGUAGES, languages)
         return instance
+
+    def _clone(self, *args, **kwargs):
+        clone = super(TransQuerySet, self)._clone()
+        clone.lang = self.lang
+        clone.languages = self.languages
+        return clone

File model_i18n/translator.py

View file
 from django.db import models
 from django.core.exceptions import ImproperlyConfigured
 from django.utils.translation import ugettext_lazy as _
+from model_i18n import managers
 from model_i18n.options import ModelTranslation
-from model_i18n import managers
 from model_i18n.exceptions import AlreadyRegistered
+from model_i18n.conf import CURRENT_LANGUAGES, ATTR_BACKUP_SUFFIX
+
 
 __all__ = ['register', 'ModelTranslation']
 
 
         Master model:
             * master_model._translation_model: Translation model
+            * master_model.switch_language: language switcher
 
         Managers:
             * See setup_manager
         """
         # Master model
         master_model._translation_model = translation_model
+        master_model.switch_language = switch_language
         # Managers
         # FIXME: We probably should we add a translation option to ignore some
         # manager (so users can create non multilingual managers)
                 new.instancemethod(getattr(managers, method_name), manager, manager.__class__))
 
 
+def switch_language(instance, lang=None):
+    """Here we overrides the default fields with their translated
+    values. We keep the default if there's no value in the translated
+    field or more than one language was requested.
+        instance.switch_language('es')
+            will load attribute values for 'es' language
+        instance.switch_language()
+            will load attribute values for master default language
+    """
+    current_languages = getattr(instance, CURRENT_LANGUAGES, None)
+
+    if current_languages: # any translation?
+        trans_meta = instance._translation_model._transmeta
+        fields = trans_meta.translatable_fields
+        if not lang or lang == trans_meta.master_language: # use defaults
+            for name in fields:
+                value = getattr(instance, '_'.join((name, ATTR_BACKUP_SUFFIX)))
+                setattr(instance, name, value)
+        elif lang in current_languages: # swtich language
+            for name in fields:
+                value = getattr(instance, '_'.join((name, lang)), None)
+                if value is not None: # Ignore None, means not translated
+                    setattr(instance, name, value)
+
 # Just one Translator instance is needed.
 _translator = Translator()