Commits

Anonymous committed 21083b6

Refactored creation of translation fields. Added a factory to handle the creation of translation fields. The factory also checks if a field is officially supported by modeltranslation and bails out early in case it doesn't. Resolves issue 37.

  • Participants
  • Parent commits bcd5d49

Comments (0)

Files changed (4)

          OneToOneField.
          (resolves issue 15)
 
+CHANGED: Refactored creation of translation fields and added handling of
+         supported fields.
+         (resolves issue 37)
+
   FIXED: Kept backwards compatibility with Django-1.0.
          (thanks to jaap, resolves issue 34)
   FIXED: Regression in south_field_triple caused by r55.

modeltranslation/fields.py

 # -*- coding: utf-8 -*-
+import sys
+
 from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
 from django.db.models.fields import Field, CharField
 from django.db.models.fields.related import (ForeignKey, OneToOneField,
                                              ManyToManyField)
 
-from modeltranslation.utils import (get_default_language,
+from modeltranslation.utils import (get_language,
+                                    get_default_language,
                                     build_localized_fieldname,
                                     build_localized_verbose_name)
 
+# List of fields which don't have to be subclassed to be supported
+STD_TRANSLATION_FIELDS = ('CharField', 'TextField', 'IntegerField',
+                          'BooleanField', 'NullBooleanField',)
+
+
+def create_translation_field(model, field_name, lang):
+    """
+    Translation field factory.
+
+    Tries to create an object in the form  ``'Translation%s' % cls_name``
+    (e.g. ``TranslationForeignKey``, ``TranslationManyToManyField``) based on
+    ``model`` and ``field_name``. The class is usually a subclass of
+    ``TranslationField`` and is supposed to be implemented in this module. If
+    the class is listed in ``STD_TRANSLATION_FIELDS`` then ``TranslationField``
+    will be used to instantiate the object. If the class is neither implemented
+    nor in ``STD_TRANSLATION_FIELDS`` ``ImproperlyConfigured`` will be raised.
+    """
+    field = model._meta.get_field(field_name)
+    cls_name = field.__class__.__name__
+    # No subclass required for text fields
+    if cls_name in STD_TRANSLATION_FIELDS:
+        return TranslationField(translated_field=field, language=lang)
+    # Try to instantiate translation field subclass
+    try:
+        translation_field = getattr(sys.modules['modeltranslation.fields'],
+                                    'Translation%s' % cls_name)
+    except AttributeError:
+        raise ImproperlyConfigured('%s is not supported by '
+                                   'modeltranslation.' % cls_name)
+    # Handle related fields
+    if cls_name in ('ForeignKey', 'OneToOneField', 'ManyToManyField'):
+        to = field.rel.to._meta.object_name
+        return translation_field(translated_field=field, language=lang, to=to)
+    # TODO: Should never be reached?
+    return TranslationField(field, lang)
+
 
 class TranslationField(Field):
     """
         # Update the dict of this field with the content of the original one
         # This might be a bit radical?! Seems to work though...
         self.__dict__.update(translated_field.__dict__)
-
-        # Common init
         self._post_init(translated_field, language)
 
     def _post_init(self, translated_field, language):
         # Copy the verbose name and append a language suffix
         # (will show up e.g. in the admin).
         self.verbose_name =\
-        build_localized_verbose_name(translated_field.verbose_name,
-                                     language)
+        build_localized_verbose_name(translated_field.verbose_name, language)
 
     def pre_save(self, model_instance, add):
         val = super(TranslationField, self).pre_save(model_instance, add)
         self.language = language
 
         self.field_name = self.translated_field.name
-        self.translated_field_name = \
-            build_localized_fieldname(self.translated_field.name,
-                                      self.language)
+        self.translated_field_name =\
+        build_localized_fieldname(self.translated_field.name,
+                                  self.language)
 
         # Dynamically add a related_name to the original field
-        translated_field.rel.related_name = \
-            '%s%s' % (self.translated_field.model._meta.module_name,
-                      self.field_name)
+        translated_field.rel.related_name =\
+        '%s%s' % (self.translated_field.model._meta.module_name,
+                  self.field_name)
 
         TranslationField.__init__(self, self.translated_field, self.language,
                                   *args, **kwargs)
 
     def _related_post_init(self):
         # Dynamically add a related_name to the translation fields
-        self.rel.related_name = \
-            '%s%s' % (self.translated_field.model._meta.module_name,
-                      self.translated_field_name)
+        self.rel.related_name =\
+        '%s%s' % (self.translated_field.model._meta.module_name,
+                  self.translated_field_name)
 
         # ForeignKey's init overrides some essential values from
         # TranslationField, they have to be reassigned.
         TranslationField._post_init(self, self.translated_field, self.language)
 
 
-class ForeignKeyTranslationField(ForeignKey, TranslationField,
-                                 RelatedTranslationField):
+class TranslationForeignKey(ForeignKey, TranslationField,
+                            RelatedTranslationField):
     def __init__(self, translated_field, language, to, to_field=None, *args,
                  **kwargs):
         self._related_pre_init(translated_field, language, *args, **kwargs)
         self._related_post_init()
 
 
-class OneToOneTranslationField(OneToOneField, TranslationField,
+class TranslationOneToOneField(OneToOneField, TranslationField,
                                RelatedTranslationField):
     def __init__(self, translated_field, language, to, to_field=None, *args,
                  **kwargs):
         self._related_post_init()
 
 
-class ManyToManyTranslationField(ManyToManyField, TranslationField,
+class TranslationManyToManyField(ManyToManyField, TranslationField,
                                  RelatedTranslationField):
     def __init__(self, translated_field, language, to, *args, **kwargs):
         self._related_pre_init(translated_field, language, *args, **kwargs)
         ManyToManyField.__init__(self, to, **kwargs)
         self._related_post_init()
+
+
+class TranslationFieldDescriptor(object):
+    """A descriptor used for the original translated field."""
+    def __init__(self, name, initial_val="", fallback_value=None):
+        """
+        The ``name`` is the name of the field (which is not available in the
+        descriptor by default - this is Python behaviour).
+        """
+        self.name = name
+        self.val = initial_val
+        self.fallback_value = fallback_value
+
+    def __set__(self, instance, value):
+        lang = get_language()
+        loc_field_name = build_localized_fieldname(self.name, lang)
+        # also update the translation field of the current language
+        setattr(instance, loc_field_name, value)
+        # update the original field via the __dict__ to prevent calling the
+        # descriptor
+        instance.__dict__[self.name] = value
+
+    def __get__(self, instance, owner):
+        if not instance:
+            raise ValueError(u"Translation field '%s' can only be accessed "
+                              "via an instance not via a class." % self.name)
+        loc_field_name = build_localized_fieldname(self.name,
+                                                   get_language())
+        if hasattr(instance, loc_field_name):
+            return getattr(instance, loc_field_name) or\
+                           (self.get_default_instance(instance) if\
+                            self.fallback_value is None else\
+                            self.fallback_value)
+
+    def get_default_instance(self, instance):
+        """
+        Returns default instance of the field. Supposed to be overidden by
+        related subclasses.
+        """
+        return instance.__dict__[self.name]
+
+
+class RelatedTranslationFieldDescriptor(TranslationFieldDescriptor):
+    def __init__(self, name, initial_val="", fallback_value=None):
+        TranslationFieldDescriptor.__init__(self, name, initial_val="",
+                                            fallback_value=None)
+
+    def get_default_instance(self, instance):
+        # TODO: Implement
+        pass
+
+
+class ManyToManyTranslationFieldDescriptor(TranslationFieldDescriptor):
+    def __init__(self, name, initial_val="", fallback_value=None):
+        TranslationFieldDescriptor.__init__(self, name, initial_val="",
+                                            fallback_value=None)
+
+    def get_default_instance(self, instance):
+        # TODO: Implement
+        pass

modeltranslation/translator.py

 from django.utils.functional import curry
 
 from modeltranslation.fields import (TranslationField,
-                                     ForeignKeyTranslationField,
-                                     OneToOneTranslationField,
-                                     ManyToManyTranslationField)
-from modeltranslation.utils import (TranslationFieldDescriptor,
-                                    RelatedTranslationFieldDescriptor,
-                                    ManyToManyTranslationFieldDescriptor,
-                                    build_localized_fieldname)
+                                     TranslationForeignKey,
+                                     TranslationOneToOneField,
+                                     TranslationManyToManyField,
+                                     TranslationFieldDescriptor,
+                                     RelatedTranslationFieldDescriptor,
+                                     ManyToManyTranslationFieldDescriptor,
+                                     create_translation_field)
+from modeltranslation.utils import build_localized_fieldname
 
 
 class AlreadyRegistered(Exception):
             localized_field_name = build_localized_fieldname(field_name, l[0])
             # Check if the model already has a field by that name
             if hasattr(model, localized_field_name):
-                raise ValueError("Error adding translation field. The model "\
-                                 "'%s' already contains a field named '%s'. "\
-                                 % (instance.__class__.__name__,
-                                    localized_field_name))
-
+                raise ValueError("Error adding translation field. Model '%s' "
+                                 "already contains a field named '%s'." %\
+                                 (instance.__class__.__name__,
+                                  localized_field_name))
+            # Create a dynamic translation field
+            translation_field = create_translation_field(model=model,\
+                                field_name=field_name, lang=l[0])
             # This approach implements the translation fields as full valid
             # django model fields and therefore adds them via add_to_class
-            field = model._meta.get_field(field_name)
-            field_class_name = field.rel.__class__.__name__
-            if field_class_name in ('ManyToOneRel', 'OneToOneRel',
-                                    'ManyToManyRel',):
-                to = field.rel.to._meta.object_name
-                if field_class_name == 'ManyToOneRel':
-                    translation_field = ForeignKeyTranslationField(\
-                                        translated_field=field,
-                                        language=l[0], to=to)
-                elif field_class_name == 'OneToOneRel':
-                    translation_field = OneToOneTranslationField(\
-                                        translated_field=field,
-                                        language=l[0], to=to)
-                elif field_class_name == 'ManyToManyRel':
-                    translation_field = ManyToManyTranslationField(\
-                                        translated_field=field,
-                                        language=l[0], to=to)
-            else:
-                translation_field = TranslationField(field, l[0])
             localized_field = model.add_to_class(localized_field_name,
                                                  translation_field)
             localized_fields[field_name].append(localized_field_name)

modeltranslation/utils.py

 def _build_localized_verbose_name(verbose_name, lang):
     return u'%s [%s]' % (verbose_name, lang)
 build_localized_verbose_name = lazy(_build_localized_verbose_name, unicode)
-
-
-class TranslationFieldDescriptor(object):
-    """A descriptor used for the original translated field."""
-    def __init__(self, name, initial_val="", fallback_value=None):
-        """
-        The ``name`` is the name of the field (which is not available in the
-        descriptor by default - this is Python behaviour).
-        """
-        self.name = name
-        self.val = initial_val
-        self.fallback_value = fallback_value
-        self.loc_field_name = ""
-
-    def __set__(self, instance, value):
-        lang = get_language()
-        loc_field_name = build_localized_fieldname(self.name, lang)
-        # also update the translation field of the current language
-        setattr(instance, loc_field_name, value)
-        # update the original field via the __dict__ to prevent calling the
-        # descriptor
-        instance.__dict__[self.name] = value
-
-    def __get__(self, instance, owner):
-        if not instance:
-            raise ValueError(u"Translation field '%s' can only be accessed "
-                              "via an instance not via a class." % self.name)
-        self.loc_field_name = build_localized_fieldname(self.name,
-                                                        get_language())
-        if hasattr(instance, self.loc_field_name):
-            return getattr(instance, self.loc_field_name) or\
-                   (self.get_default_instance(instance) if\
-                    self.fallback_value is None else\
-                    self.fallback_value)
-
-    def get_default_instance(self, instance):
-        """
-        Returns default instance of the field. Supposed to be overidden by
-        related subclasses.
-        """
-        return instance.__dict__[self.name]
-        
-
-class RelatedTranslationFieldDescriptor(TranslationFieldDescriptor):
-    def __init__(self, name, initial_val="", fallback_value=None):
-        TranslationFieldDescriptor.__init__(self, name, initial_val="",
-                                            fallback_value=None)
-
-    def get_default_instance(self, instance):
-        # TODO: Implement
-        #instance_id = instance.__dict__['%s_id' % self.name]
-        pass
-
-
-class ManyToManyTranslationFieldDescriptor(TranslationFieldDescriptor):
-    def __init__(self, name, initial_val="", fallback_value=None):
-        TranslationFieldDescriptor.__init__(self, name, initial_val="",
-                                            fallback_value=None)
-
-    def get_default_instance(self, instance):
-        # TODO: Implement
-        pass