Source code for shoop.utils.multilanguage_model_form
# -*- coding: utf-8 -*-
# This file is part of Shoop.
#
# Copyright (c) 2012-2016, Shoop Ltd. All rights reserved.
#
# This source code is licensed under the AGPLv3 license found in the
# LICENSE file in the root directory of this source tree.
from __future__ import unicode_literals
import copy
from collections import defaultdict
import six
from django.forms.models import model_to_dict, ModelForm
from django.utils.translation import get_language
from parler.forms import TranslatableModelForm
from shoop.utils.i18n import get_language_name
[docs]def to_language_codes(languages):
languages = languages or (get_language(),)
if languages and isinstance(languages[0], (list, tuple)):
# `languages` looks like a `settings.LANGUAGES`, so fix it
languages = [code for (code, name) in languages]
return languages
[docs]class MultiLanguageModelForm(TranslatableModelForm):
def _get_translation_model(self):
return self._meta.model._parler_meta.root_model
def __init__(self, **kwargs):
self.languages = to_language_codes(kwargs.pop("languages", ()))
self.default_language = kwargs.pop("default_language", getattr(self, 'language', get_language()))
if self.default_language not in self.languages:
raise ValueError("Language %r not in %r" % (self.default_language, self.languages))
self.required_languages = kwargs.pop("required_languages", [self.default_language])
opts = self._meta
translations_model = self._get_translation_model()
object_data = {}
# We're not mutating the existing fields, so the shallow copy should be okay
self.base_fields = self.base_fields.copy()
self.translation_fields = [
f for (f, _) in translations_model._meta.get_fields_with_model()
if f.name not in ('language_code', 'master', 'id') and f.name in self.base_fields
]
self.trans_field_map = defaultdict(dict)
self.trans_name_map = defaultdict(dict)
self.translated_field_names = []
self.non_default_languages = sorted(set(self.languages) - set([self.default_language]))
self.language_names = dict((lang, get_language_name(lang)) for lang in self.languages)
for f in self.translation_fields:
base = self.base_fields.pop(f.name, None)
if not base:
continue
for lang in self.languages:
language_field = copy.deepcopy(base)
language_field_name = "%s__%s" % (f.name, lang)
language_field.required = language_field.required and (lang in self.required_languages)
language_field.label = "%s [%s]" % (language_field.label, self.language_names.get(lang))
self.base_fields[language_field_name] = language_field
self.trans_field_map[lang][language_field_name] = f
self.trans_name_map[lang][f.name] = language_field_name
self.translated_field_names.append(language_field_name)
instance = kwargs.get("instance")
initial = kwargs.get("initial")
if instance is not None:
assert isinstance(instance, self._meta.model)
current_translations = dict(
(trans.language_code, trans)
for trans in translations_model.objects.filter(master=instance)
)
object_data = {}
for lang, trans in six.iteritems(current_translations):
model_dict = model_to_dict(trans, opts.fields, opts.exclude)
object_data.update(("%s__%s" % (fn, lang), f) for (fn, f) in six.iteritems(model_dict))
if initial is not None:
object_data.update(initial)
kwargs["initial"] = object_data
super(MultiLanguageModelForm, self).__init__(**kwargs)
def __getitem__(self, key):
try:
return super(MultiLanguageModelForm, self).__getitem__(key)
except KeyError:
return super(MultiLanguageModelForm, self).__getitem__(key + "__" + self.default_language)
def _save_translations(self, instance, data):
translations_model = self._get_translation_model()
current_translations = dict(
(trans.language_code, trans)
for trans
in translations_model.objects.filter(master=instance, language_code__in=self.languages)
)
for lang, field_map in six.iteritems(self.trans_field_map):
current_translations[lang] = translation = (
current_translations.get(lang) or translations_model(master=instance, language_code=lang)
)
translation_fields = dict((src_name, data.get(src_name)) for src_name in field_map)
for src_name, field in six.iteritems(field_map):
field.save_form_data(translation, translation_fields[src_name])
self._save_translation(instance, translation)
def _save_translation(self, instance, translation):
"""
Process saving a single translation.
This could be used to delete unnecessary/cleared translations or skip
saving translations altogether.
:param instance: Parent model instance
:type instance: django.db.models.Model
:param translation: Translation model
:type translation: parler.models.TranslatedFieldsModelBase
"""
translation.save()
[docs] def save(self, commit=True):
self._set_fields_for_language(self.default_language)
self.pre_master_save(self.instance)
self.instance = self._save_master(commit)
self._save_translations(self.instance, self.cleaned_data)
return self.instance
def _set_fields_for_language(self, language):
self.instance.set_current_language(language)
for field in self.translation_fields:
value = self.cleaned_data["%s__%s" % (field.name, language)]
field.save_form_data(self.instance, value)
def _save_master(self, commit=True):
# We skip TranslatableModelForm on purpose!
return super(ModelForm, self).save(True)
def _get_cleaned_data_without_translations(self):
"""
Get cleaned data without translated fields.
"""
translated_field_names = set(self.translated_field_names)
return dict(
(k, v) for (k, v) in six.iteritems(self.cleaned_data)
if k not in translated_field_names)