使用JSON的Django多语言文本字段

我最近问了这个问题
Custom Django MultilingualTextField model field,但我发现没有理由不这样做,所以我创建了一个支持多语言文本的模型字段,以当前语言自动返回文本.这基本上是以json格式将自定义Language对象存储到数据库的字段.这是代码:

Github:https://github.com/james4388/django-multilingualfield

Ussage:

from django.db import models
from multilingualfield import MLTextField, MLHTMLField

class MyModel(models.Model):
    text = MLTextField()
    html = MLHTMLField()

像普通文本字段一样使用它,翻译是基于系统语言的自动翻译(translation.get_language)

>>>from django.utils import translation
>>>translation.active('en')

>>>m = MyModal.objects.create(text='Hello world',html='<b>Hello world</b>');
>>>m.text
Hello world
>>>translation.active('fr')
>>>m.text       #Auto fallback to first language (if any).
Hello world
>>>m.text.value('Bonjour')
>>>m.text.value('Ciao','es')
>>>m.text
Bonjour
>>>m.save()
>>>m.text.get_available_language()
['en', 'fr', 'es']
>>>m.text.remove_language('en')

Field.py

from __future__ import unicode_literals

from django.core.exceptions import ValidationError
from django.conf import settings
from django.db import models, DatabaseError, transaction
from django.utils.translation import ugettext_lazy as _, get_language
from django.utils import six

try:
    import json
except ImportError:
    from django.utils import simplejson as json

def get_base_language(lang):
    if '-' in lang:
        return lang.split('-')[0]
    return lang

def get_current_language(base=True):
    l = get_language()
    if base:
        return get_base_language(l)
    return l

from .widgets import MultilingualWidget, MultilingualHTMLWidget
from .forms import MultilingualTextFormField, MultilingualHTMLFormField
from .language import LanguageText

class MultilingualTextField(six.with_metaclass(models.SubfieldBase, models.Field)):
    """
    A field that support multilingual text for your model
    """
    default_error_messages = {
        'invalid': _("'%s' is not a valid JSON string.")
    }
    description = "Multilingual text field"

    def __init__(self, *args, **kwargs):
        self.lt_max_length = kwargs.pop('max_length',-1)
        self.default_language = kwargs.get('default_language', get_current_language())
        super(MultilingualTextField, self).__init__(*args, **kwargs)


    def formfield(self, **kwargs):
        defaults = {
            'form_class': MultilingualTextFormField,
            'widget': MultilingualWidget
        }
        defaults.update(**kwargs)
        return super(MultilingualTextField, self).formfield(**defaults)

    def validate(self, value, model_instance):
        if not self.null and value is None:
            raise ValidationError(self.error_messages['null'])
        try:
            self.get_prep_value(value)
        except:
            raise ValidationError(self.error_messages['invalid'] % value)

    def get_internal_type(self):
        return 'TextField'


    def db_type(self, connection):
        return 'text'

    def to_python(self, value):
        if isinstance(value, six.string_types):
            if value == "" or value is None:
                if self.null:
                    return None
                if self.blank:
                    return ""
            try:
                valuejson = json.loads(value)
                Lang = LanguageText(max_length=self.lt_max_length,default_language=self.default_language)
                Lang.values = valuejson
                return Lang
            except ValueError:
                try:
                    Lang = LanguageText(value,language=None,max_length=self.lt_max_length,default_language=self.default_language)
                    return Lang
                except:
                    msg = self.error_messages['invalid'] % value
                    raise ValidationError(msg)
        return value

    def get_db_prep_value(self, value, connection=None, prepared=None):
        return self.get_prep_value(value)

    def get_prep_value(self, value):
        if value is None:
            if not self.null and self.blank:
                return ""
            return None
        if isinstance(value, six.string_types):
            value = LanguageText(value,language=None,max_length=self.lt_max_length,default_language=self.default_language)
        if isinstance(value, LanguageText):
            value.max_length = self.lt_max_length
            value.default_language = self.default_language
            return json.dumps(value.values)
        return None

    def get_prep_lookup(self, lookup_type, value):
        if lookup_type in ["exact", "iexact"]:
            return self.to_python(self.get_prep_value(value))
        if lookup_type == "in":
            return [self.to_python(self.get_prep_value(v)) for v in value]
        if lookup_type == "isnull":
            return value
        if lookup_type in ["contains", "icontains"]:
            if isinstance(value, (list, tuple)):
                raise TypeError("Lookup type %r not supported with argument of %s" % (
                    lookup_type, type(value).__name__
                ))
                # Need a way co combine the values with '%', but don't escape that.
                return self.get_prep_value(value)[1:-1].replace(', ', r'%')
            if isinstance(value, dict):
                return self.get_prep_value(value)[1:-1]
            return self.to_python(self.get_prep_value(value))
        raise TypeError('Lookup type %r not supported' % lookup_type)

    def value_to_string(self, obj):
        return self._get_val_from_obj(obj)

Forms.py

from django import forms
from django.utils import simplejson as json

from .widgets import MultilingualWidget, MultilingualHTMLWidget
from .language import LanguageText

class MultilingualTextFormField(forms.CharField):

    widget = MultilingualWidget

    def __init__(self, *args, **kwargs):
        kwargs['widget'] = MultilingualWidget
        super(MultilingualTextFormField, self).__init__(*args, **kwargs)

    def clean(self, value):
        """
        The default is to have a TextField, and we will decode the string
        that comes back from this. However, another use of this field is
        to store a list of values, and use these in a MultipleSelect
        widget. So, if we have an object that isn't a string, then for now
        we will assume that is where it has come from.
        """
        value = super(MultilingualTextFormField, self).clean(value)
        if not value:
            return value
        if isinstance(value, basestring):
            try:
                valuejson = json.loads(value)
                Lang = LanguageText()
                Lang.values = valuejson
                return Lang
            except ValueError:
                try:
                    Lang = LanguageText(value,language=None)
                    return Lang
                except:
                    raise forms.ValidationError(
                        'JSON decode error: %s' % (unicode(exc),)
                    )
        else:
            return value

language.py中的语言对象

from __future__ import unicode_literals

from django.core.exceptions import ValidationError
from django.conf import settings
from django.db import models, DatabaseError, transaction
from django.utils.translation import ugettext_lazy as _, get_language

try:
    import json
except ImportError:
    from django.utils import simplejson as json

def get_base_language(lang):
    if '-' in lang:
        return lang.split('-')[0]
    return lang

def get_current_language(base=True):
    l = get_language()
    if base:
        return get_base_language(l)
    return l


class LanguageText(object):
    '''
        JSON text field blah blah blah
    '''
    values = {}
    default_language = None
    max_length = -1

    def __init__(self, value=None, language=None, default_language=None, max_length=-1):
        self.max_length = max_length
        self.default_language = default_language
        self.values = {}
        if value is not None:
            self.value(value,language)

    def __call__(self, value=None, language=None):
        self.value(value,language)
        return self

    def get_available_language(self):
        return self.values.keys()

    def get_current_language(self, base=False):
        return get_current_language(base)

    def remove_language(self, lang):
        try:
            return self.values.pop(lang)
        except:
            pass

    def has_language(self, lang):
        return self.values.has_key(lang)

    def get(self, language=None, fallback=True):
        if language is None:
            curr_lang = get_current_language(False)
        else:
            curr_lang = language
        curr_lang_base = get_current_language(True)
        if curr_lang in self.values:
            return self.values[curr_lang]
        if not fallback:
            return None
        if curr_lang_base in self.values:
            return self.values[curr_lang_base]
        if self.default_language in self.values:
            return self.values[self.default_language]
        try:
            first_lang = self.values.keys()[0]
            return self.values[first_lang]
        except:
            pass
        return None

    def value(self, value=None, language=None):
        if value is None:   #Get value
            return self.get(language)
        else: #Set value
            if language is None:
                language = get_current_language(False)
            if self.max_length != -1:
                value = value[:self.max_length]
            self.values[language] = value
            return None

    def __unicode__(self):
        return self.value()

    def __str__(self):
        return unicode(self.value()).encode('utf-8')

    def __repr__(self):
        return unicode(self.value()).encode('utf-8')

widgets.py

from django import forms
from django.utils import simplejson as json
from django.conf import settings
from .language import LanguageText
from django.template import loader, Context

class MultilingualWidget(forms.Textarea):

    def __init__(self, *args, **kwargs):
        forms.Widget.__init__(self, *args, **kwargs)

    def render(self, name, value, attrs=None):
        if value is None: #New create or edit none
            vjson = '{}'
            aLang = []
            Lang = '[]'
            Langs = json.dumps(dict(settings.LANGUAGES))
            t = loader.get_template('multilingualtextarea.html')
            c = Context({"data":value,"vjson":vjson,"lang":Lang,"langs":Langs,"langobjs":settings.LANGUAGES,"fieldname":name})
            return t.render(c)
        if isinstance(value, LanguageText):
            vjson = json.dumps(value.values)
            aLang = value.get_available_language()
            Lang = json.dumps(aLang)
            Langs = json.dumps(dict(settings.LANGUAGES))
            t = loader.get_template('multilingualtextarea.html')
            c = Context({"data":value,"vjson":vjson,"lang":Lang,"langs":Langs,"langobjs":settings.LANGUAGES,"fieldname":name})
            return t.render(c)
        return "Invalid data '%s'" % value

所以我想知道这是一个好方法吗?我为什么不这样做? Plz的帮助

最佳答案 代码对我来说很好看.

唯一可能影响性能的是频繁的json编码/解码……但是,除非你在资源最少的服务器上面对成千上万的用户,否则不会产生重大影响.

您链接的上一个问题包含一些注释,指出使用其他方法添加其他语言可能更容易.但最终 – 这是个人偏好和可维护性之间的混合.如果它符合您的项目目标,我看不出任何理由不按照您编码的方式进行.

提供证据证明您的实施是最好的几乎是不可能的.也就是说,除非您自己通过在生产服务器上创建不同的,基于非json的实现和基准测试来证明它.您会注意到常规机器上的差异相当小.然而,只有个别数字才能提供实际证据,并且可以帮助您确定它是否已经“调整”和“资源友好”,足以满足您项目的目的.我认为它符合您的需求……但这只是我的2美分.

点赞