Collection of helpers for multilingual websites.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

143 lines
4.6 KiB

# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django import forms
from django.conf import settings
from django.contrib.postgres.fields import JSONField
from django.db import models
from django.db.models import NOT_PROVIDED
from django.utils.text import format_lazy
from shared.utils.translation import get_language, lang_suffix
def get_translated_value(fieldname):
def translated_value(obj):
language = get_language()
val = obj.__dict__[lang_suffix(language, fieldname)]
if not val:
other_languages = list(dict(settings.LANGUAGES).keys())
other_languages.remove(language)
for lang in other_languages:
val = obj.__dict__[lang_suffix(lang, fieldname)]
if val:
break
return val
return translated_value
class TranslatableFieldMixin:
"""
Make a Field subclass translatable, i.e. it automatically provides field duplicates
for each language defined in settings.LANGUAGES.
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
Usage:
class TranslatableRichTextField(TranslatableFieldMixin, RichTextField):
base_class = RichTextField
extra_parameter_names = ['config_name', 'extra_plugins', 'external_plugin_resources']
"""
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().__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
params = {
'blank': _blank,
'choices': self.choices,
'db_column': None,
'db_index': self.db_index,
'db_tablespace': self.db_tablespace,
'default': self.default,
'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.remote_field,
'serialize': self.serialize,
'unique': self.unique,
}
# TODO If null=False/blank=False add validator which checks that at
# least one field has a value
# Because we never allow NULL set empty string as default
if params['default'] == NOT_PROVIDED:
params['default'] = ''
for n in self.extra_parameter_names:
params[n] = getattr(self, n, None)
if self.db_column:
params['db_column'] = lang_suffix(lang_code, self.db_column)
# 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]
class LocalizedFieldClass(self.base_class):
lang = lang_code
def formfield(self, **kwargs):
formfield = super().formfield(**kwargs)
formfield.widget.attrs.update({
'lang': self.lang,
})
return formfield
localized_field = LocalizedFieldClass(
format_lazy("{} ({})", self.verbose_name, lang_code),
**params
)
localized_field.contribute_to_class(
cls,
"%s%s" % (name, lang_suffix(lang_code)),
)
setattr(cls, name, property(get_translated_value(name)))
class TranslatableCharField(TranslatableFieldMixin, models.CharField):
pass
class TranslatableSlugField(TranslatableFieldMixin, models.SlugField):
pass
# TODO TranslatableFormField not used, remove?
class TranslatableFormField(forms.fields.CharField):
pass
class TranslatableTextField(TranslatableFieldMixin, models.TextField):
pass
class TranslatableJSONField(TranslatableFieldMixin, JSONField):
extra_parameter_names = ['encoder']