|
|
|
@ -9,63 +9,42 @@ from django.utils.translation import string_concat
|
|
|
|
|
from shared.utils.translation import get_language, lang_suffix |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TranslatableCharField(models.CharField): |
|
|
|
|
def get_translated_value(name): |
|
|
|
|
def translated_value(obj): |
|
|
|
|
language = get_language() |
|
|
|
|
val = obj.__dict__["%s%s" % (name, lang_suffix(language))] |
|
|
|
|
if not val: |
|
|
|
|
val = obj.__dict__["%s%s" % (name, lang_suffix(settings.LANGUAGE_CODE))] |
|
|
|
|
return val |
|
|
|
|
return translated_value |
|
|
|
|
|
|
|
|
|
def __init__(self, verbose_name=None, **kwargs): |
|
|
|
|
self._blank = kwargs.get("blank", False) |
|
|
|
|
self._editable = kwargs.get("editable", True) |
|
|
|
|
|
|
|
|
|
super(TranslatableCharField, self).__init__(verbose_name, **kwargs) |
|
|
|
|
class TranslatableFieldMixin: |
|
|
|
|
""" |
|
|
|
|
Make a Field subclass translatable, i.e. it automatically provides field duplicates |
|
|
|
|
for each language defined in settings.LANGUAGES. |
|
|
|
|
|
|
|
|
|
def contribute_to_class(self, cls, name, private_only=False): |
|
|
|
|
for lang_code, lang_name in settings.LANGUAGES: |
|
|
|
|
if lang_code == settings.LANGUAGE_CODE: |
|
|
|
|
_blank = self._blank |
|
|
|
|
else: |
|
|
|
|
_blank = True |
|
|
|
|
Parameters: |
|
|
|
|
base_class |
|
|
|
|
optional, is None first base class which is a subclass of Django's Field class is used |
|
|
|
|
extra_parameter_names |
|
|
|
|
optional, attributes of the original field to be copied to the localized fields |
|
|
|
|
|
|
|
|
|
localized_field = models.CharField( |
|
|
|
|
string_concat(self.verbose_name, " (%s)" % lang_code), |
|
|
|
|
name=self.name, |
|
|
|
|
primary_key=self.primary_key, |
|
|
|
|
max_length=self.max_length, |
|
|
|
|
unique=self.unique, |
|
|
|
|
blank=_blank, |
|
|
|
|
null=False, # intentionally ignored |
|
|
|
|
db_index=self.db_index, |
|
|
|
|
rel=self.rel, |
|
|
|
|
default=self.default or "", |
|
|
|
|
editable=self._editable, |
|
|
|
|
serialize=self.serialize, |
|
|
|
|
choices=self.choices, |
|
|
|
|
help_text=self.help_text, |
|
|
|
|
db_column=None, |
|
|
|
|
db_tablespace=self.db_tablespace |
|
|
|
|
) |
|
|
|
|
Usage: |
|
|
|
|
|
|
|
|
|
localized_field.contribute_to_class( |
|
|
|
|
cls, |
|
|
|
|
"%s%s" % (name, lang_suffix(lang_code)), |
|
|
|
|
) |
|
|
|
|
class TranslatableRichTextField(TranslatableFieldMixin, RichTextField): |
|
|
|
|
base_class = RichTextField |
|
|
|
|
extra_parameter_names = ['config_name', 'extra_plugins', 'external_plugin_resources'] |
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
def translated_value(self): |
|
|
|
|
# For empty / non-existing translation fall back to main field |
|
|
|
|
language = get_language() |
|
|
|
|
val = self.__dict__["%s%s" % (name, lang_suffix(language))] |
|
|
|
|
if not val: |
|
|
|
|
val = self.__dict__["%s%s" % (name, lang_suffix(settings.LANGUAGE_CODE))] |
|
|
|
|
return val |
|
|
|
|
|
|
|
|
|
setattr(cls, name, property(translated_value)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TranslatableTextField(models.TextField): |
|
|
|
|
base_class = None |
|
|
|
|
extra_parameter_names = [] |
|
|
|
|
|
|
|
|
|
def __init__(self, verbose_name=None, **kwargs): |
|
|
|
|
self._blank = kwargs.get("blank", False) |
|
|
|
|
self._editable = kwargs.get("editable", True) |
|
|
|
|
|
|
|
|
|
super(TranslatableTextField, self).__init__(verbose_name, **kwargs) |
|
|
|
|
super().__init__(verbose_name, **kwargs) |
|
|
|
|
|
|
|
|
|
def contribute_to_class(self, cls, name, private_only=False): |
|
|
|
|
for lang_code, lang_name in settings.LANGUAGES: |
|
|
|
@ -74,23 +53,36 @@ class TranslatableTextField(models.TextField):
|
|
|
|
|
else: |
|
|
|
|
_blank = True |
|
|
|
|
|
|
|
|
|
localized_field = models.TextField( |
|
|
|
|
params = { |
|
|
|
|
'blank': _blank, |
|
|
|
|
'choices': self.choices, |
|
|
|
|
'db_column': None, |
|
|
|
|
'db_index': self.db_index, |
|
|
|
|
'db_tablespace': self.db_tablespace, |
|
|
|
|
'default': self.default or "", |
|
|
|
|
'editable': self._editable, |
|
|
|
|
'help_text': self.help_text, |
|
|
|
|
'max_length': self.max_length, |
|
|
|
|
'name': self.name, |
|
|
|
|
'null': False, # intentionally ignored |
|
|
|
|
'primary_key': self.primary_key, |
|
|
|
|
'rel': self.rel, |
|
|
|
|
'serialize': self.serialize, |
|
|
|
|
'unique': self.unique, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for n in self.extra_parameter_names: |
|
|
|
|
params[n] = getattr(self, n, None) |
|
|
|
|
|
|
|
|
|
# TODO Move this logic to a meta class? |
|
|
|
|
if not self.base_class: |
|
|
|
|
# Get first base class which is a subclass of Django's Field |
|
|
|
|
self.base_class = [f for f in self.__class__.__bases__ |
|
|
|
|
if issubclass(f, models.Field)][0] |
|
|
|
|
|
|
|
|
|
localized_field = self.base_class( |
|
|
|
|
string_concat(self.verbose_name, " (%s)" % lang_code), |
|
|
|
|
name=self.name, |
|
|
|
|
primary_key=self.primary_key, |
|
|
|
|
max_length=self.max_length, |
|
|
|
|
unique=self.unique, |
|
|
|
|
blank=_blank, |
|
|
|
|
null=False, # intentionally ignored |
|
|
|
|
db_index=self.db_index, |
|
|
|
|
rel=self.rel, |
|
|
|
|
default=self.default or "", |
|
|
|
|
editable=self._editable, |
|
|
|
|
serialize=self.serialize, |
|
|
|
|
choices=self.choices, |
|
|
|
|
help_text=self.help_text, |
|
|
|
|
db_column=None, |
|
|
|
|
db_tablespace=self.db_tablespace |
|
|
|
|
**params |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
localized_field.contribute_to_class( |
|
|
|
@ -98,61 +90,18 @@ class TranslatableTextField(models.TextField):
|
|
|
|
|
"%s%s" % (name, lang_suffix(lang_code)), |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
def translated_value(self): |
|
|
|
|
language = get_language() |
|
|
|
|
val = self.__dict__["%s%s" % (name, lang_suffix(language))] |
|
|
|
|
if not val: |
|
|
|
|
val = self.__dict__["%s%s" % (name, lang_suffix(settings.LANGUAGE_CODE))] |
|
|
|
|
return val |
|
|
|
|
setattr(cls, name, property(get_translated_value(name))) |
|
|
|
|
|
|
|
|
|
setattr(cls, name, property(translated_value)) |
|
|
|
|
|
|
|
|
|
class TranslatableCharField(TranslatableFieldMixin, models.CharField): |
|
|
|
|
pass |
|
|
|
|
|
|
|
|
|
class TranslatableJSONField(JSONField): |
|
|
|
|
|
|
|
|
|
def __init__(self, verbose_name=None, **kwargs): |
|
|
|
|
self._blank = kwargs.get("blank", False) |
|
|
|
|
self._editable = kwargs.get("editable", True) |
|
|
|
|
|
|
|
|
|
super().__init__(verbose_name, **kwargs) |
|
|
|
|
|
|
|
|
|
def contribute_to_class(self, cls, name, private_only=False): |
|
|
|
|
for lang_code, lang_name in settings.LANGUAGES: |
|
|
|
|
if lang_code == settings.LANGUAGE_CODE: |
|
|
|
|
_blank = self._blank |
|
|
|
|
else: |
|
|
|
|
_blank = True |
|
|
|
|
class TranslatableTextField(TranslatableFieldMixin, models.TextField): |
|
|
|
|
pass |
|
|
|
|
|
|
|
|
|
localized_field = JSONField( |
|
|
|
|
string_concat(self.verbose_name, " (%s)" % lang_code), |
|
|
|
|
name=self.name, |
|
|
|
|
primary_key=self.primary_key, |
|
|
|
|
max_length=self.max_length, |
|
|
|
|
unique=self.unique, |
|
|
|
|
blank=_blank, |
|
|
|
|
null=False, # intentionally ignored |
|
|
|
|
db_index=self.db_index, |
|
|
|
|
rel=self.rel, |
|
|
|
|
default=self.default or "", |
|
|
|
|
editable=self._editable, |
|
|
|
|
serialize=self.serialize, |
|
|
|
|
choices=self.choices, |
|
|
|
|
help_text=self.help_text, |
|
|
|
|
db_column=None, |
|
|
|
|
db_tablespace=self.db_tablespace, |
|
|
|
|
encoder=self.encoder |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
localized_field.contribute_to_class( |
|
|
|
|
cls, |
|
|
|
|
"%s%s" % (name, lang_suffix(lang_code)), |
|
|
|
|
) |
|
|
|
|
class TranslatableJSONField(TranslatableFieldMixin, JSONField): |
|
|
|
|
extra_parameter_names = ['encoder'] |
|
|
|
|
|
|
|
|
|
def translated_value(self): |
|
|
|
|
language = get_language() |
|
|
|
|
val = self.__dict__["%s%s" % (name, lang_suffix(language))] |
|
|
|
|
if not val: |
|
|
|
|
val = self.__dict__["%s%s" % (name, lang_suffix(settings.LANGUAGE_CODE))] |
|
|
|
|
return val |
|
|
|
|
|
|
|
|
|
setattr(cls, name, property(translated_value)) |
|
|
|
|