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('../')