Source code for modeltranslation_xliff.admin

import logging
from collections import OrderedDict
from django.contrib import messages
from django.core.exceptions import ValidationError
from django.db.models import Model, QuerySet
from django.http.request import HttpRequest
from django.http.response import HttpResponse, HttpResponseRedirect, \
    HttpResponseNotAllowed
from django.conf.urls import url
from django.utils.translation import ugettext_lazy as _
from modeltranslation.fields import TranslationField
from modeltranslation.settings import DEFAULT_LANGUAGE, AVAILABLE_LANGUAGES
from .utils import create_xliff, import_xliff


[docs]class XliffExchangeMixin: """ XLIFF exchange for django-modeltranslation This mixin adds a custom admin action for exporting translatable content to XLIFF format and a file upload form for importing translations from translated XLIFF files. Usage example:: from django.contrib import admin from modeltranslation.admin import TranslationAdmin from modeltranslation_xliff.admin import XliffExchangeMixin from .models import MyModel @admin.register(MyModel) class MyModelAdmin(XliffExchangeMixin, TranslationAdmin): pass """ change_list_template = 'modeltranslation_xliff/change_list.html' actions = ['export_xliff'] @staticmethod def _get_object_trans_source(obj): # type: (Model) -> dict """ Extract translatable content from a model object :param obj: Django model object :return: dictionary with translatable content """ fields = obj._meta.get_fields() translatable_fields = [] for f in fields: name, *lang = f.name.split('_', 1) # .replace('ind', 'id') fixes Indonesian langugage code lang = lang[0].replace('_', '-').replace('ind', 'id') if lang else '' if isinstance(f, TranslationField) and lang == DEFAULT_LANGUAGE: # OrderedDict is used to mitigate different dict behavior # in Py3.5 and Py3.6+ where dict key order is preserved. # This is needed to pass tests on all Python versions. f_dict = OrderedDict() f_dict['name'] = name f_dict['value'] = getattr(obj, f.name) translatable_fields.append(f_dict) obj_dict = OrderedDict() obj_dict['id'] = str(obj.pk) obj_dict['fields'] = translatable_fields return obj_dict def _get_model_trans_source(self, queryset): # type: (QuerySet) -> dict """ Extract translatable content from a queryset :param queryset: queryset for model objects to translate :return: dictionary with translatable content """ translatable_objects = [] for obj in queryset: translatable_objects.append(self._get_object_trans_source(obj)) model_dict = OrderedDict() model_dict['name'] = self.model.__name__ model_dict['language'] = DEFAULT_LANGUAGE model_dict['objects'] = translatable_objects return model_dict @staticmethod def _get_language_code(language): # type: (str) -> str """ Get actual language code for translated content :param language: language code from a translated XLIFF :return: actual language code from Django project settings """ if language in AVAILABLE_LANGUAGES: # Language codes match, we are good. return language for lang in AVAILABLE_LANGUAGES: # Try to find matching language without a country code if language[:2] == lang[:2]: return lang raise ValidationError( _('Unknown translation language: "{}"!').format(language) ) def _update_translations(self, translation_data): # type: (dict) -> None """ Update translatable models content from imported XLIFF :param translation_data: imported translations from a XLIFF """ if translation_data['name'] != self.model.__name__: raise ValidationError( _('Uploaded XLIFF is for different model: "{}"!').format( translation_data['name'] ) ) language = self._get_language_code( translation_data['language'] ).replace('-', '_') object_pks = (int(obj['id']) for obj in translation_data['objects']) queryset = self.model.objects.filter(pk__in=object_pks) fields = [f['name'] + '_' + language for f in translation_data['objects'][0]['fields']] for item, obj in zip(queryset, translation_data['objects']): for i, field in enumerate(obj['fields']): setattr(item, fields[i], field['value']) item.save(update_fields=fields) def export_xliff(self, request, queryset): # type: (HttpRequest, QuerySet) -> HttpResponse """ Export XLIFF view :param request: request instance. :param queryset: a queryset for model objects to translate. :return: response containing a XLIFF file with content to translate """ response = HttpResponse( create_xliff(self._get_model_trans_source(queryset)).encode('utf-8') ) response['Content-Type'] = 'application/x-xliff-xml' response['Content-Disposition'] = \ 'attachment; filename="{}.xlf"'.format(self.model.__name__.lower()) return response export_xliff.short_description = _('Export to XLIFF') def get_urls(self): # type: () -> list urls = [ url(r'import-xliff/$', self.import_xliff, name='import_xliff') ] + super().get_urls() return urls def import_xliff(self, request): # type: (HttpRequest) -> HttpResponse """ Import XLIFF view :param request: request instance :return: redirect response """ if request.method != 'POST': return HttpResponseNotAllowed('POST') try: fo = request.FILES.get('_upload-xliff') if not fo: raise ValidationError(_('No XLIFF file uploaded!')) xliff = fo.read() translation_data = import_xliff(xliff) self._update_translations(translation_data) except ValidationError as ex: self.message_user(request, ex.message, level=messages.ERROR) except Exception as ex: logging.exception('Error while importing XLIFF!') self.message_user( request, _('Unexpected error while importing XLIFF: {}').format(str(ex)), level=messages.ERROR ) else: self.message_user( request, _('Translation for "{}" was imported successfully.'.format( translation_data['language'] )) ) return HttpResponseRedirect('../')