mirror of
https://github.com/Hopiu/django-modeltranslation.git
synced 2026-04-10 00:20:59 +00:00
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.
This commit is contained in:
parent
5d5bf067ad
commit
406abfb89f
4 changed files with 134 additions and 111 deletions
|
|
@ -8,6 +8,10 @@
|
|||
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.
|
||||
|
|
|
|||
|
|
@ -1,13 +1,53 @@
|
|||
# -*- 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):
|
||||
"""
|
||||
|
|
@ -31,8 +71,6 @@ 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):
|
||||
|
|
@ -54,8 +92,7 @@ class TranslationField(Field):
|
|||
# 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)
|
||||
|
|
@ -106,31 +143,31 @@ class RelatedTranslationField(object):
|
|||
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)
|
||||
|
|
@ -138,7 +175,7 @@ class ForeignKeyTranslationField(ForeignKey, TranslationField,
|
|||
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):
|
||||
|
|
@ -147,9 +184,69 @@ class OneToOneTranslationField(OneToOneField, TranslationField,
|
|||
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
|
||||
|
|
|
|||
|
|
@ -7,13 +7,14 @@ from django.db.models.base import ModelBase
|
|||
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):
|
||||
|
|
@ -58,32 +59,15 @@ def add_localized_fields(model):
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -56,65 +56,3 @@ def build_localized_fieldname(field_name, lang):
|
|||
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
|
||||
|
|
|
|||
Loading…
Reference in a new issue