diff --git a/shared/multilingual/utils/fields.py b/shared/multilingual/utils/fields.py index 464b699..1563e6e 100644 --- a/shared/multilingual/utils/fields.py +++ b/shared/multilingual/utils/fields.py @@ -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))