Commits

jonasvp  committed c4ee555

Get current language from threadlocals and return the corresponding i18n field

Instead of returning a Proxy class for LocalizedFields, we can get the current language
using Django's get_language() and return the actual field. That makes it easier and more
transparent to work with translated fields, as we cann call all field methods that Django
provides.

You can still access a specific language version by using underscores or the
model.get_localized() call.

  • Participants
  • Parent commits 398e3be

Comments (0)

Files changed (1)

File composite_field/l10n.py

-from copy import deepcopy
+from django.conf import settings
+from django.db import models
+from django.utils.translation import get_language as django_get_language
 
-from django.conf import settings 
-from django.db.models.fields import Field, CharField, TextField, FloatField
-from django.utils.functional import lazy
+from composite_field.base import CompositeField
 
-from . import CompositeField
 
+LANGUAGES = [lang for lang, name in settings.LANGUAGES]
 
-LANGUAGES = map(lambda lang: lang[0], getattr(settings, 'LANGUAGES', ()))
+def localized_field_names(field):
+    return tuple(['%s_%s' % (field, lang) for lang in LANGUAGES])
+
+def get_language():
+    # return str not unicode so we can use it in a kwargs dictionary
+    return str(django_get_language())
+
+def get_localized(self, lang, name):
+    '''
+    Gets patched onto a Model with Localized fields, so we can get the
+    value of localized field "name" in language "lang".
+    '''
+    try:
+        attr = getattr(self, '%s_%s' % (name, lang))
+    except AttributeError,  e:
+        raise AttributeError(
+            'Either field "%s" does not exist, or language "%s" is not defined for this model. (%s)' % \
+                (name, lang, e.message)
+        )
+    return attr
+
+def set_localized(self, lang, name, value):
+    '''
+    Gets patched onto a Model with Localized fields, so we can set
+    localized field "name" in language "lang" to "value".
+    '''
+    try:
+        attr = setattr(self, '%s_%s' % (name, lang), value)
+    except AttributeError, e:
+        raise AttributeError(
+            'Either field "%s" does not exist, or language "%s" is not defined for this model. (%s)' % \
+                (name, lang, e.message)
+        )
+    return attr
 
 
 class LocalizedField(CompositeField):
-
-    def __init__(self, field_class, languages=LANGUAGES, **kwargs):
-        if not languages:
-            raise RuntimeError('Set LANGUAGES in your settings.py or pass a non empty "languages" argument before using LocalizedCharField')
+    def __init__(self, field_class, *args, **kwargs):
+        '''
+        Adds a model field of type "field_class" with translations for all settings.LANGUAGES.
+        Extra parameter "fallback" can have one of these values:
+           True: always fallback to the default language (settings.LANGUAGE_CODE) in case the 
+                 field is empty (not translated)
+           False (default): never fallback, even when the object is not translated
+        '''
         super(LocalizedField, self).__init__()
         self.verbose_name = kwargs.pop('verbose_name', None)
-        for language in languages:
-            self[language] = field_class(**kwargs)
+        self.fallback = kwargs.pop('fallback', None)
+
+        # we can't check for blanks, one language might always be blank
+        # TODO: find a way to check if it's filled in any one language
+        kwargs.pop('blank', False)
+
+        for language in LANGUAGES:
+            self[language] = field_class(blank=True, *args, **kwargs)
 
     def contribute_to_class(self, cls, field_name):
         if self.verbose_name is None:
-            self.verbose_name = field_name.replace('_', ' ')
+            self.verbose_name = field_name.replace('_', ' ').capitalize()
+
         for language in self:
-            # verbose_name must be lazy in order for the admin to show the
-            # translated verbose_names of the fields
-            self[language].verbose_name = lazy(lambda language: u'%s (%s)' % (
-                    self.verbose_name, language), unicode)(language)
+            self[language].verbose_name = u'%s (%s)' % (self.verbose_name, language)
+            # Save a reference to the composite field for later use
+            self[language].composite_field = self
+
         super(LocalizedField, self).contribute_to_class(cls, field_name)
 
-    def get_proxy(self, model):
-        return LocalizedField.Proxy(self, model)
+        # patch some helper functions onto the class
+        cls.get_localized = get_localized
+        cls.set_localized = set_localized
 
-    class Proxy(CompositeField.Proxy):
+    def get(self, model):
+        # get current value
+        translation = getattr(model, self.prefix + get_language())
 
-        def __nonzero__(self):
-            return bool(unicode(self))
+        if self.fallback == False:
+            # we don't fallback, return the value
+            return translation
 
-        def __unicode__(self):
-            from django.utils.translation import get_language
-            language = get_language()
-            translation = None
-            # 1. complete language code
-            translation = getattr(self, language, None)
-            if translation is not None:
-                return translation
-            # 2. base of language code
-            if '-' in language:
-                base_lang = language.split('-')[0]
-                translation = getattr(self, base_lang, None)
-                if translation is not None:
-                    return translation
-            # 3. first available translation
-            for language in settings.LANGUAGES:
-                getattr(self, base_lang, None)
-                if translation is not None:
-                    return translation
-            return None
+        if translation or not self.fallback:
+            # show translation only if it exists or we have disabled fallback
+            return translation
+
+        # fallback to default language
+        return getattr(model, self.prefix + settings.LANGUAGE_CODE)
+
+    def set(self, model, value):
+        setattr(model, self.prefix + get_language(), value)
 
 
 class LocalizedCharField(LocalizedField):
-
-    def __init__(self, **kwargs):
-        super(LocalizedCharField, self).__init__(CharField, **kwargs)
-
+    def __init__(self, *args, **kwargs):
+        super(LocalizedCharField, self).__init__(models.CharField, *args, **kwargs)
 
 class LocalizedTextField(LocalizedField):
+    def __init__(self, *args, **kwargs):
+        super(LocalizedTextField, self).__init__(models.TextField, *args, **kwargs)
 
-    def __init__(self, **kwargs):
-        super(LocalizedTextField, self).__init__(TextField, **kwargs)
+class LocalizedFileField(LocalizedField):
+    def __init__(self, *args, **kwargs):
+        super(LocalizedFileField, self).__init__(models.FileField, *args, **kwargs)
+
+class LocalizedImageField(LocalizedFileField):
+    def __init__(self, *args, **kwargs):
+        super(LocalizedImageField, self).__init__(models.ImageField, *args, **kwargs)
+
+class LocalizedBooleanField(LocalizedField):
+    def __init__(self, *args, **kwargs):
+        super(LocalizedBooleanField, self).__init__(models.BooleanField, *args, **kwargs)
+
+class LocalizedDateField(LocalizedField):
+    def __init__(self, *args, **kwargs):
+        super(LocalizedDateField, self).__init__(models.DateField, *args, **kwargs)
+
+class LocalizedForeignKey(LocalizedField):
+    def __init__(self, *args, **kwargs):
+        super(LocalizedForeignKey, self).__init__(models.ForeignKey, *args, **kwargs)