Skip to content

Commit

Permalink
Merge c93691b into 169f210
Browse files Browse the repository at this point in the history
  • Loading branch information
ta2-1 committed Jan 12, 2017
2 parents 169f210 + c93691b commit c63b419
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 27 deletions.
10 changes: 10 additions & 0 deletions docs/server/commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,16 @@ A file or a .zip of files is provided as output. The file headers include a
revision counter to assist Pootle to detetmine how to handle subsequent uploads
of the file.

Available options:

.. django-admin-option:: --tmx

.. versionadded:: 2.8.0

Export every translation project as one zipped .tmx file into :setting:`MEDIA_ROOT`
directory.


.. django-admin:: import

import
Expand Down
62 changes: 39 additions & 23 deletions pootle/apps/import_export/management/commands/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
from pootle_project.models import Project
from pootle_store.models import Store

from ...utils import TPTMXExporter


class Command(PootleCommand):
help = "Export a Project, Translation Project, or path. " \
Expand All @@ -30,6 +32,19 @@ def add_arguments(self, parser):
dest="pootle_path",
help="Export a single file",
)
parser.add_argument(
"--tmx",
action="store_true",
dest="export_tmx",
default=False,
help="Export one translation project into a single TMX file",
)
parser.add_argument(
"--overwrite",
action="store_true",
default=False,
help="Overwrite already exported TMX files",
)

def _create_zip(self, stores, prefix):
with open("%s.zip" % (prefix), "wb") as f:
Expand All @@ -40,41 +55,42 @@ def _create_zip(self, stores, prefix):
self.stdout.write("Created %s\n" % (f.name))

def handle_all(self, **options):
project_query = Project.objects.all()

if self.projects:
project_query = project_query.filter(code__in=self.projects)

if options['pootle_path'] is not None:
if options['export_tmx']:
raise CommandError("--path option can't be used with --tmx option")
return self.handle_path(options['pootle_path'])

# support exporting an entire project
if self.projects and not self.languages:
for project in project_query:
if self.projects and not self.languages and not options['export_tmx']:
for project in Project.objects.filter(code__in=self.projects):
self.handle_project(project)
return

# Support exporting an entire language
if self.languages and not self.projects:
if self.languages and not self.projects and not options['export_tmx']:
for language in Language.objects.filter(code__in=self.languages):
self.handle_language(language)
return

for project in project_query.iterator():
tp_query = project.translationproject_set \
.order_by("language__code")

if self.languages:
tp_query = tp_query.filter(language__code__in=self.languages)

for tp in tp_query.iterator():
self.do_translation_project(tp, **options)

def handle_translation_project(self, translation_project, **options_):
stores = translation_project.stores.live()
prefix = "%s-%s" % (translation_project.project.code,
translation_project.language.code)
self._create_zip(stores, prefix)
return super(Command, self).handle_all(**options)

def handle_translation_project(self, translation_project, **options):
if options['export_tmx']:
overwrite = options.get('overwrite', False)
exporter = TPTMXExporter(translation_project)
if not exporter.has_changes() and not overwrite:
self.stdout.write(
'Translation project (%s) has not been changed.' %
translation_project)
return False

filename = exporter.export()
self.stdout.write('File "%s" has been saved.' % filename)
else:
stores = translation_project.stores.live()
prefix = "%s-%s" % (translation_project.project.code,
translation_project.language.code)
self._create_zip(stores, prefix)

def handle_project(self, project):
stores = Store.objects.live().filter(
Expand Down
83 changes: 83 additions & 0 deletions pootle/apps/import_export/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,22 @@
# AUTHORS file for copyright and authorship information.

import logging
import os

from io import BytesIO
from zipfile import ZipFile

from translate.storage.factory import getclass
from translate.storage import tmx

from django.conf import settings
from django.utils.functional import cached_property

from pootle.core.delegate import revision
from pootle.i18n.gettext import ugettext_lazy as _
from pootle_app.models.permissions import check_user_permission
from pootle_statistics.models import SubmissionTypes
from pootle_store.constants import TRANSLATED
from pootle_store.models import Store

from .exceptions import (FileImportError, MissingPootlePathError,
Expand Down Expand Up @@ -62,3 +72,76 @@ def import_file(f, user=None):
# This should not happen!
logger.error("Error importing file: %s", str(e))
raise FileImportError(_("There was an error uploading your file"))


class TPTMXExporter(object):

def __init__(self, context):
self.context = context

@property
def exported_revision(self):
return revision.get(self.context.__class__)(
self.context).get(key="pootle.offline.tm")

@cached_property
def revision(self):
return revision.get(self.context.__class__)(
self.context.directory).get(key="stats")

def relative_path(self):
return "offline_tm/%s/%s" % (
self.context.language.code,
self.filename)

def get_url(self):
return "%s/%s" % (settings.MEDIA_URL, self.relative_path)

def update_exported_revision(self):
if self.has_changes():
revision.get(self.context.__class__)(
self.context).set(keys=["pootle.offline.tm"],
value=self.revision)

def has_changes(self):
return self.revision != self.exported_revision

@property
def directory(self):
return os.path.join(settings.MEDIA_ROOT,
'offline_tm',
self.context.language.code)

@property
def filename(self):
filename = self.context.project.fullname.replace(' ', '_')
filename = ".".join([filename, self.revision, 'tmx'])
return "%s.zip" % filename

@property
def abs_filepath(self):
return os.path.join(self.directory, self.filename)

def export(self):
source_language = self.context.project.source_language.code
target_language = self.context.language.code

if not os.path.exists(self.directory):
os.makedirs(self.directory)

tmxfile = tmx.tmxfile()
for store in self.context.stores.live().iterator():
for unit in store.units.filter(state=TRANSLATED):
tmxfile.addtranslation(unit.source, source_language,
unit.target, target_language,
unit.developer_comment)

bs = BytesIO()
tmxfile.serialize(bs)
with open(self.abs_filepath, "wb") as f:
with ZipFile(f, "w") as zf:
zf.writestr(self.filename, bs.getvalue())

self.update_exported_revision()

return self.abs_filepath
5 changes: 1 addition & 4 deletions pootle/apps/pootle_revision/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,7 @@ def revision_context(self):


class TPRevision(RevisionContext):

@property
def revision_context(self):
return self.context.directory.revisions
pass


class RevisionUpdater(object):
Expand Down
3 changes: 3 additions & 0 deletions pootle/apps/pootle_translationproject/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import logging

from django.conf import settings
from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
Expand All @@ -25,6 +26,7 @@
from pootle_language.models import Language
from pootle_misc.checks import excluded_filters
from pootle_project.models import Project
from pootle_revision.models import Revision
from pootle_store.constants import PARSED
from pootle_store.util import absolute_real_path, relative_real_path
from staticpages.models import StaticPage
Expand Down Expand Up @@ -156,6 +158,7 @@ class TranslationProject(models.Model, CachedTreeItem):
db_index=True, editable=False)
creation_time = models.DateTimeField(auto_now_add=True, db_index=True,
editable=False, null=True)
revisions = GenericRelation(Revision)

objects = TranslationProjectManager()

Expand Down
17 changes: 17 additions & 0 deletions tests/commands/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from django.core.management import call_command
from django.core.management.base import CommandError

from pootle.core.delegate import revision


@pytest.mark.cmd
@pytest.mark.django_db
Expand Down Expand Up @@ -81,3 +83,18 @@ def test_export_path_unknown():
with pytest.raises(CommandError) as e:
call_command('export', '--path=/af/unknown')
assert "Could not find store matching '/af/unknown'" in str(e)


@pytest.mark.cmd
@pytest.mark.django_db
def test_export_tmx_tp(capfd, tp0):
"""Export a project"""
lang_code = tp0.language.code
prj_code = tp0.project.code
call_command('export', '--tmx', '--project=%s' % prj_code,
'--language=%s' % lang_code)
out, err = capfd.readouterr()
rev = revision.get(tp0.__class__)(tp0.directory).get(key="stats")
filename = '%s/%s.%s.tmx.zip' % (
lang_code, tp0.project.fullname.replace(' ', '_'), rev)
assert filename in out

0 comments on commit c63b419

Please sign in to comment.