Merge lp:~henninge/launchpad/devel-710591-importer into lp:launchpad

Proposed by Henning Eggers
Status: Superseded
Proposed branch: lp:~henninge/launchpad/devel-710591-importer
Merge into: lp:launchpad
Prerequisite: lp:~henninge/launchpad/devel-710591-setcurrenttranslation-extension
Diff against target: 860 lines (+489/-117)
10 files modified
lib/lp/registry/model/distribution.py (+9/-6)
lib/lp/translations/model/potmsgset.py (+6/-4)
lib/lp/translations/scripts/upload_translations.py (+96/-0)
lib/lp/translations/tests/test_translationmessage.py (+175/-75)
lib/lp/translations/tests/test_translationpolicy.py (+38/-8)
lib/lp/translations/utilities/tests/test_file_importer.py (+78/-17)
lib/lp/translations/utilities/tests/test_translation_sharing_info.py (+40/-0)
lib/lp/translations/utilities/translation_import.py (+26/-5)
lib/lp/translations/utilities/translationsharinginfo.py (+2/-2)
scripts/rosetta/upload-translations.py (+19/-0)
To merge this branch: bzr merge lp:~henninge/launchpad/devel-710591-importer
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) code Approve
Ian Booth (community) release-critical Approve
Review via email: mp+48682@code.launchpad.net

This proposal has been superseded by a proposal from 2011-02-11.

Description of the change

= Summary =

This is the final branch to fix bug 710591. It brings together
the functions and methods that the previous two introduced.

== Proposed fix ==

Add a boolean property to FileImporter that makes use
has_upstream_template to determine which "accept" method to
use.
Replace the use of "approve" in the importer with the
"accept" mtethods.

== Pre-implementation notes ==

I still refer to my chat with Danilo about this but also
Jeroen and I talked a bit while he reviewed the last
branch.

== Implementation details ==

Pretty straight forward implementation, I'd say.

== Tests ==

bin/test -vvcm lp.translations.utilities.tests.test_file_importer

I also ran this to check that anything related to "import" might
be affected:

bin/test -vvcm lp.translations -t import

== Demo and Q/A ==

Import on a source package and see what happens.

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/translations/interfaces/translationmessage.py
  lib/lp/translations/tests/test_translationmessage.py
  lib/lp/testing/factory.py
  lib/lp/translations/utilities/tests/test_file_importer.py
  lib/lp/translations/model/potmsgset.py
  lib/lp/translations/utilities/translation_import.py
  lib/lp/translations/model/translationmessage.py

./lib/lp/translations/tests/test_translationmessage.py
     365: E302 expected 2 blank lines, found 1

To post a comment you must log in.
Revision history for this message
Henning Eggers (henninge) wrote :

Hi Ian,
thanks for the approval of the previous branch. I am requesting r-c for this final branch for the same bug, even though it did not have a code review yet just to make you aware of it.
Cheers,
Henning

Revision history for this message
Ian Booth (wallyworld) wrote :

Approved so long as the formal code review is ok and it's fully QAed as soon as possible after deployment to qastaging.

review: Approve (release-critical)
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Beautiful.

In accordance to my personal prove-you're-really-reading-it policy, I managed to find one very very minor thing to comment on:

107 + # - The by_maintainer flag must be set.

You may want to mention where this flag is (on the import-queue entry), since it's not where the reader would be most likely to look for it. You could say something like "the translation must come from an upload with the by_maintainer flag set."

Jeroen

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/model/distribution.py'
2--- lib/lp/registry/model/distribution.py 2011-02-09 19:25:43 +0000
3+++ lib/lp/registry/model/distribution.py 2011-02-11 14:44:33 +0000
4@@ -205,6 +205,9 @@
5 HasTranslationImportsMixin,
6 )
7 from lp.translations.model.translationpolicy import TranslationPolicyMixin
8+from lp.translations.utilities.translationsharinginfo import (
9+ has_upstream_template,
10+ )
11
12
13 class Distribution(SQLBase, BugTargetBase, MakesAnnouncements,
14@@ -1845,11 +1848,11 @@
15 assert sourcepackage is not None, (
16 "Translations sharing policy requires a SourcePackage.")
17
18- sharing_productseries = sourcepackage.productseries
19- if sharing_productseries is None:
20- # There is no known upstream series. Take the uploader's
21- # word for whether these are upstream translations (in which
22- # case they're shared) or not.
23+ if not has_upstream_template(
24+ sourcepackage.distroseries, sourcepackage.sourcepackagename):
25+ # There is no known upstream template or series. Take the
26+ # uploader's word for whether these are upstream translations
27+ # (in which case they're shared) or not.
28 # What are the consequences if that value is incorrect? In
29 # the case where translations from upstream are purportedly
30 # from Ubuntu, we miss a chance at sharing when the package
31@@ -1861,7 +1864,7 @@
32 # translations for upstream.
33 return purportedly_upstream
34
35- upstream_product = sharing_productseries.product
36+ upstream_product = sourcepackage.productseries.product
37 return upstream_product.invitesTranslationEdits(person, language)
38
39
40
41=== modified file 'lib/lp/translations/model/potmsgset.py'
42--- lib/lp/translations/model/potmsgset.py 2011-02-04 17:14:20 +0000
43+++ lib/lp/translations/model/potmsgset.py 2011-02-11 14:44:33 +0000
44@@ -796,11 +796,12 @@
45 template, pofile.language, template.translation_side)
46 other = self.getOtherTranslation(
47 pofile.language, template.translation_side)
48- if other is not None:
49- other.is_current_upstream = False
50- if current is None or other is None:
51+ if current is None or other is None or current == other:
52 translator = suggestion.submitter
53 potranslations = dictify_translations(suggestion.all_msgstrs)
54+ if other is not None:
55+ # Steal flag beforehand.
56+ other.is_current_upstream = False
57 self._setTranslation(
58 pofile, translator, suggestion.origin, potranslations,
59 share_with_other_side=True,
60@@ -808,8 +809,9 @@
61 lock_timestamp=lock_timestamp)
62 else:
63 # Make it only current in upstream.
64- suggestion.is_current_upstream = True
65 if suggestion != other:
66+ other.is_current_upstream = False
67+ suggestion.is_current_upstream = True
68 pofile.markChanged(translator=suggestion.submitter)
69
70 def _cloneAndDiverge(self, original_message, pofile):
71
72=== added file 'lib/lp/translations/scripts/upload_translations.py'
73--- lib/lp/translations/scripts/upload_translations.py 1970-01-01 00:00:00 +0000
74+++ lib/lp/translations/scripts/upload_translations.py 2011-02-11 14:44:33 +0000
75@@ -0,0 +1,96 @@
76+# Copyright 2011 Canonical Ltd. This software is licensed under the
77+# GNU Affero General Public License version 3 (see the file LICENSE).
78+
79+__metaclass__ = type
80+
81+__all__ = [
82+ 'UploadPackageTranslations',
83+ ]
84+
85+import os
86+
87+from zope.component import getUtility
88+
89+from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
90+from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet
91+from lp.services.scripts.base import (
92+ LaunchpadScript,
93+ LaunchpadScriptFailure,
94+ )
95+from lp.translations.interfaces.translationimportqueue import (
96+ ITranslationImportQueue,
97+ )
98+
99+
100+class UploadPackageTranslations(LaunchpadScript):
101+ """Upload translations for given distribution package."""
102+ description = "Upload translation files for a package."
103+
104+ def add_my_options(self):
105+ """See `LaunchpadScript`."""
106+ self.parser.add_option('-d', '--distribution', dest='distro',
107+ help="Distribution to upload for.", default='ubuntu')
108+ self.parser.add_option('-s', '--series', dest='distroseries',
109+ help="Distribution release series to upload for.")
110+ self.parser.add_option('-p', '--package', dest='package',
111+ help="Name of source package to upload to.")
112+ self.parser.add_option('-l', '--dry-run', dest='dryrun',
113+ action='store_true', default=False,
114+ help="Pretend to upload, but make no actual changes.")
115+
116+ def main(self):
117+ """See `LaunchpadScript`."""
118+ self._setDistroDetails()
119+ self._setPackage()
120+
121+ if self.options.dryrun:
122+ self.logger.info("Dry run. Not really uploading anything.")
123+
124+ queue = getUtility(ITranslationImportQueue)
125+ rosetta_team = getUtility(ILaunchpadCelebrities).rosetta_experts
126+
127+ for filename in self.args:
128+ if not os.access(filename, os.R_OK):
129+ self.logger.info("Skipping: %s" % filename)
130+ continue
131+ self.logger.info("Uploading: %s." % filename)
132+ content = open(filename).read()
133+ queue.addOrUpdateEntry(
134+ filename, content, True, rosetta_team,
135+ sourcepackagename = self.sourcepackagename,
136+ distroseries = self.distroseries)
137+ self._commit()
138+
139+ self.logger.info("Done.")
140+
141+ def _commit(self):
142+ """Commit transaction (or abort if dry run)."""
143+ if self.txn:
144+ if self.options.dryrun:
145+ self.txn.abort()
146+ else:
147+ self.txn.commit()
148+
149+ def _setDistroDetails(self):
150+ """Figure out the `Distribution`/`DistroSeries` to act upon."""
151+ # Avoid circular imports.
152+ from lp.registry.interfaces.distribution import IDistributionSet
153+
154+ distroset = getUtility(IDistributionSet)
155+ self.distro = distroset.getByName(self.options.distro)
156+
157+ if not self.options.distroseries:
158+ raise LaunchpadScriptFailure(
159+ "Specify a distribution release series.")
160+
161+ self.distroseries = self.distro.getSeries(self.options.distroseries)
162+
163+ def _setPackage(self):
164+ """Find `SourcePackage` of given name."""
165+ # Avoid circular imports.
166+ if not self.options.package:
167+ raise LaunchpadScriptFailure("No package specified.")
168+
169+ nameset = getUtility(ISourcePackageNameSet)
170+
171+ self.sourcepackagename = nameset.queryByName(self.options.package)
172
173=== modified file 'lib/lp/translations/tests/test_translationmessage.py'
174--- lib/lp/translations/tests/test_translationmessage.py 2011-02-04 18:04:14 +0000
175+++ lib/lp/translations/tests/test_translationmessage.py 2011-02-11 14:44:33 +0000
176@@ -362,6 +362,7 @@
177
178 self.assertEqual([], karmarecorder.karma_events)
179
180+
181 class TestAcceptFromUpstreamImportOnPackage(TestCaseWithFactory):
182 """Tests for `TranslationMessage.acceptFromUpstreamImportOnPackage`.
183
184@@ -371,100 +372,199 @@
185
186 layer = ZopelessDatabaseLayer
187
188+ def _getStates(self, *messages):
189+ """Get is_current_* states for messages.
190+
191+ :param messages: A list of messages to get state for.
192+ :returns: List of tuples of two boolean values for the
193+ values of (is_current_ubuntu, is_current_upstream) for each
194+ message.
195+ """
196+ return [
197+ (message.is_current_ubuntu, message.is_current_upstream)
198+ for message in messages]
199+
200+ def _makeMessages(self, pofile,
201+ identical_ubuntu=None, identical_upstream=None,
202+ is_tracking=False):
203+ """ Create a suggestion and possible pre-existing translations.
204+
205+ The two identical_* parameters are tri-state:
206+ - If None, do not create such a message.
207+ - If True, the suggestion is identical to this message.
208+ - If False, the suggestion is different from this message.
209+
210+ :param pofile: The pofile to create messages in.
211+ :param identical_ubuntu: If and how to create the ubuntu message.
212+ :param identical upstream: If and how to create the upstream
213+ message.
214+ :param is_trackging: Used if both messages are created to indicate
215+ that they should be identical to each other.
216+ :returns: One, two or three messages, as requested.
217+ """
218+ ubuntu = None
219+ upstream = None
220+
221+ potmsgset = self.factory.makePOTMsgSet(potemplate=pofile.potemplate)
222+
223+ if identical_upstream is not None:
224+ upstream = self.factory.makeCurrentTranslationMessage(
225+ potmsgset=potmsgset, pofile=pofile, current_other=True)
226+ upstream.is_current_ubuntu = False
227+ if identical_ubuntu is not None:
228+ if is_tracking or (identical_ubuntu and identical_upstream):
229+ assert upstream is not None, (
230+ "Don't use is_tracking without identical_upstream")
231+ upstream.is_current_ubuntu = True
232+ ubuntu = upstream
233+ else:
234+ ubuntu = self.factory.makeCurrentTranslationMessage(
235+ potmsgset=potmsgset, pofile=pofile)
236+ if identical_upstream:
237+ translations = upstream.translations
238+ elif identical_ubuntu:
239+ translations = ubuntu.translations
240+ else:
241+ translations = None
242+ suggestion = self.factory.makeSuggestion(
243+ pofile=pofile, potmsgset=potmsgset, translations=translations)
244+
245+ return [message for message in (suggestion, ubuntu, upstream)
246+ if message is not None]
247+
248 def test_accept_activates_message_if_untranslated(self):
249 # An untranslated message accepts an imported translation.
250 pofile = self.factory.makePOFile(side=TranslationSide.UBUNTU)
251- suggestion = self.factory.makeSuggestion(pofile=pofile)
252- reviewer = self.factory.makePerson()
253- self.assertFalse(suggestion.is_current_ubuntu)
254- self.assertFalse(suggestion.is_current_upstream)
255+ suggestion = self._makeMessages(pofile)[0]
256
257 suggestion.acceptFromUpstreamImportOnPackage(pofile)
258
259 # Messages are always accepted on the other side, too.
260- self.assertTrue(suggestion.is_current_ubuntu)
261- self.assertTrue(suggestion.is_current_upstream)
262+ self.assertEqual([(True, True)], self._getStates(suggestion))
263
264 def test_accept_no_previously_imported(self):
265 # If there was already a current translation, but no previously
266 # imported one, it is disabled when a suggestion is accepted.
267- pofile, potmsgset = self.factory.makePOFileAndPOTMsgSet(
268- side=TranslationSide.UBUNTU)
269- suggestion = self.factory.makeSuggestion(
270- pofile=pofile, potmsgset=potmsgset)
271- incumbent_message = self.factory.makeCurrentTranslationMessage(
272- pofile=pofile, potmsgset=potmsgset)
273-
274- self.assertTrue(incumbent_message.is_current_ubuntu)
275- self.assertFalse(suggestion.is_current_ubuntu)
276-
277- suggestion.acceptFromUpstreamImportOnPackage(pofile)
278-
279- self.assertFalse(incumbent_message.is_current_ubuntu)
280- self.assertTrue(suggestion.is_current_ubuntu)
281- # Messages are always accepted on the other side, too.
282- self.assertTrue(suggestion.is_current_upstream)
283+ pofile = self.factory.makePOFile(side=TranslationSide.UBUNTU)
284+ (suggestion, incumbent_message) = self._makeMessages(
285+ pofile, identical_ubuntu=False)
286+
287+ suggestion.acceptFromUpstreamImportOnPackage(pofile)
288+
289+ self.assertEqual(
290+ [(True, True), (False, False)],
291+ self._getStates(suggestion, incumbent_message))
292+
293+ def test_accept_upstream_no_ubuntu(self):
294+ # If there was already an upstream translation, but no ubuntu
295+ # one, the suggestion replaces both.
296+ pofile = self.factory.makePOFile(side=TranslationSide.UBUNTU)
297+ (suggestion, upstream_message) = self._makeMessages(
298+ pofile, identical_upstream=False)
299+
300+ suggestion.acceptFromUpstreamImportOnPackage(pofile)
301+
302+ self.assertEqual(
303+ [(True, True), (False, False)],
304+ self._getStates(suggestion, upstream_message))
305
306 def test_accept_previously_imported(self):
307- # If there was already a current translation, and a previously
308- # imported one, the current translation is left untouched.
309- pofile, potmsgset = self.factory.makePOFileAndPOTMsgSet(
310- side=TranslationSide.UBUNTU)
311- imported_message = self.factory.makeCurrentTranslationMessage(
312- pofile=pofile, potmsgset=potmsgset, current_other=True)
313- imported_message.is_current_ubuntu = False
314-
315- suggestion = self.factory.makeSuggestion(
316- pofile=pofile, potmsgset=potmsgset)
317- incumbent_message = self.factory.makeCurrentTranslationMessage(
318- pofile=pofile, potmsgset=potmsgset)
319-
320- self.assertTrue(incumbent_message.is_current_ubuntu)
321- self.assertFalse(suggestion.is_current_ubuntu)
322- self.assertTrue(imported_message.is_current_upstream)
323-
324- suggestion.acceptFromUpstreamImportOnPackage(pofile)
325-
326- self.assertTrue(incumbent_message.is_current_ubuntu)
327- self.assertFalse(suggestion.is_current_ubuntu)
328- # Messages are always accepted on the other side, too.
329- self.assertFalse(imported_message.is_current_upstream)
330- self.assertTrue(suggestion.is_current_upstream)
331-
332- def test_accept_current_message(self):
333- # Accepting a message that's already current does nothing on this
334- # side but makes sure the other side's flag is set.
335- pofile = self.factory.makePOFile(side=TranslationSide.UBUNTU)
336- translation = self.factory.makeCurrentTranslationMessage(
337- pofile=pofile)
338- self.assertTrue(translation.is_current_ubuntu)
339- self.assertFalse(translation.is_current_upstream)
340-
341- translation.acceptFromUpstreamImportOnPackage(pofile)
342-
343- self.assertTrue(translation.is_current_ubuntu)
344- self.assertTrue(translation.is_current_upstream)
345-
346- def test_accept_current_and_imported_message(self):
347+ # If there was already an ubuntu translation, and an upstream
348+ # one, the ubuntu translation is left untouched.
349+ pofile = self.factory.makePOFile(side=TranslationSide.UBUNTU)
350+ (suggestion, ubuntu_message, upstream_message) = self._makeMessages(
351+ pofile, identical_ubuntu=False, identical_upstream=False)
352+
353+ suggestion.acceptFromUpstreamImportOnPackage(pofile)
354+
355+ # The suggestion is accepted as the upstream translation.
356+ self.assertEqual(
357+ [(False, True), (True, False), (False, False)],
358+ self._getStates(suggestion, ubuntu_message, upstream_message))
359+
360+ def test_accept_previously_imported_tracking(self):
361+ # If there was already an ubuntu translation, and an identical
362+ # upstream one, the new suggestion replaces both.
363+ pofile = self.factory.makePOFile(side=TranslationSide.UBUNTU)
364+ (suggestion, ubuntu_message, upstream_message) = self._makeMessages(
365+ pofile, identical_ubuntu=False, identical_upstream=False,
366+ is_tracking=True)
367+
368+ suggestion.acceptFromUpstreamImportOnPackage(pofile)
369+
370+ self.assertEqual(
371+ [(True, True), (False, False), (False, False)],
372+ self._getStates(suggestion, ubuntu_message, upstream_message))
373+
374+ def test_accept_different_upstream(self):
375+ # If there was already an identcal ubuntu translation, and a
376+ # different upstream one, the new suggestion will become both.
377+ pofile = self.factory.makePOFile(side=TranslationSide.UBUNTU)
378+ (suggestion, ubuntu_message, upstream_message) = self._makeMessages(
379+ pofile, identical_ubuntu=True, identical_upstream=False)
380+
381+ suggestion.acceptFromUpstreamImportOnPackage(pofile)
382+
383+ self.assertEqual(
384+ [(True, True), (True, True), (False, False)],
385+ self._getStates(suggestion, ubuntu_message, upstream_message))
386+
387+ def test_accept_different_ubuntu(self):
388+ # If there was already an identcal upstream translation, and a
389+ # different ubuntu one, the ubuntu translation is not touched.
390+ pofile = self.factory.makePOFile(side=TranslationSide.UBUNTU)
391+ (suggestion, ubuntu_message, upstream_message) = self._makeMessages(
392+ pofile, identical_ubuntu=False, identical_upstream=True)
393+
394+ suggestion.acceptFromUpstreamImportOnPackage(pofile)
395+
396+ self.assertEqual(
397+ [(False, True), (True, False), (False, True)],
398+ self._getStates(suggestion, ubuntu_message, upstream_message))
399+
400+ def test_accept_ubuntu_message(self):
401+ # Accepting a message that's identical to the ubuntu message makes
402+ # sure that the message also becomes current upstream.
403+ pofile = self.factory.makePOFile(side=TranslationSide.UBUNTU)
404+ (suggestion, ubuntu_message) = self._makeMessages(
405+ pofile, identical_ubuntu=True)
406+
407+ suggestion.acceptFromUpstreamImportOnPackage(pofile)
408+
409+ self.assertEqual(
410+ [(True, True), (True, True)],
411+ self._getStates(suggestion, ubuntu_message))
412+
413+ def test_accept_upstream_message(self):
414+ # Accepting a message that's identical to the upstream message makes
415+ # sure that the message also becomes current in ubuntu.
416+ pofile = self.factory.makePOFile(side=TranslationSide.UBUNTU)
417+ (suggestion, upstream_message) = self._makeMessages(
418+ pofile, identical_upstream=True)
419+
420+ suggestion.acceptFromUpstreamImportOnPackage(pofile)
421+
422+ self.assertEqual(
423+ [(True, True), (True, True)],
424+ self._getStates(suggestion, upstream_message))
425+
426+ def test_accept_ubuntu_and_upstream_message(self):
427 # Accepting a message that's already current and was also imported
428 # does nothing.
429 pofile = self.factory.makePOFile(side=TranslationSide.UBUNTU)
430- translation = self.factory.makeCurrentTranslationMessage(
431- pofile=pofile, current_other=True)
432- self.assertTrue(translation.is_current_ubuntu)
433- self.assertTrue(translation.is_current_upstream)
434-
435- translation.acceptFromUpstreamImportOnPackage(pofile)
436-
437- self.assertTrue(translation.is_current_ubuntu)
438- self.assertTrue(translation.is_current_upstream)
439+ (suggestion, ubuntu_message, upstream_message) = self._makeMessages(
440+ pofile, identical_ubuntu=True, identical_upstream=True)
441+
442+ suggestion.acceptFromUpstreamImportOnPackage(pofile)
443+
444+ self.assertEqual(
445+ [(True, True), (True, True), (True, True)],
446+ self._getStates(suggestion, ubuntu_message, upstream_message))
447
448 def test_accept_detects_conflict(self):
449 pofile = self.factory.makePOFile(side=TranslationSide.UBUNTU)
450- current = self.factory.makeCurrentTranslationMessage(pofile=pofile)
451- potmsgset = current.potmsgset
452- suggestion = self.factory.makeSuggestion(
453- pofile=pofile, potmsgset=potmsgset)
454+ (suggestion, ubuntu_message) = self._makeMessages(
455+ pofile, identical_ubuntu=False)
456 old = datetime.now(UTC) - timedelta(days=1)
457
458 self.assertRaises(
459
460=== modified file 'lib/lp/translations/tests/test_translationpolicy.py'
461--- lib/lp/translations/tests/test_translationpolicy.py 2010-11-10 05:04:16 +0000
462+++ lib/lp/translations/tests/test_translationpolicy.py 2011-02-11 14:44:33 +0000
463@@ -379,33 +379,63 @@
464 self.user = self.factory.makePerson()
465 self.language = self.factory.makeLanguage()
466
467- def _doesPackageShare(self, sourcepackage, from_upstream=False):
468+ def _doesPackageShare(self, sourcepackage, by_maintainer=False):
469 """Does this `SourcePackage` share with upstream?"""
470 distro = sourcepackage.distroseries.distribution
471 return distro.sharesTranslationsWithOtherSide(
472 self.user, self.language, sourcepackage=sourcepackage,
473- purportedly_upstream=from_upstream)
474+ purportedly_upstream=by_maintainer)
475
476 def test_product_always_shares(self):
477 product = self.factory.makeProduct()
478 self.assertTrue(
479 product.sharesTranslationsWithOtherSide(self.user, self.language))
480
481- def test_distribution_shares_only_if_invited(self):
482+ def _makePackageAndProductSeries(self):
483 package = self.factory.makeSourcePackage()
484 self.factory.makePackagingLink(
485 sourcepackagename=package.sourcepackagename,
486 distroseries=package.distroseries)
487- product = package.productseries.product
488+ return (package, package.productseries)
489+
490+ def test_distribution_shares_only_if_invited_with_template(self):
491+ # With an upstream template, translations will be shared if the
492+ # product invites edits.
493+ package, productseries = self._makePackageAndProductSeries()
494+ product = productseries.product
495+ self.factory.makePOTemplate(productseries=productseries)
496
497 product.translationpermission = TranslationPermission.OPEN
498 self.assertTrue(self._doesPackageShare(package))
499 product.translationpermission = TranslationPermission.CLOSED
500 self.assertFalse(self._doesPackageShare(package))
501
502- def test_unlinked_package_shares_only_upstream_translations(self):
503+ def test_distribution_shares_not_without_template(self):
504+ # Without an upstream template, translations will not be shared
505+ # if they do not originate from uploads done by the maintainer.
506+ package, productseries = self._makePackageAndProductSeries()
507+ product = productseries.product
508+
509+ product.translationpermission = TranslationPermission.OPEN
510+ self.assertFalse(self._doesPackageShare(package))
511+ product.translationpermission = TranslationPermission.CLOSED
512+ self.assertFalse(self._doesPackageShare(package))
513+
514+ def test_distribution_shares_only_by_maintainer_without_template(self):
515+ # Without an upstream template, translations will be shared
516+ # if they do originate from uploads done by the maintainer.
517+ package, productseries = self._makePackageAndProductSeries()
518+ product = productseries.product
519+
520+ product.translationpermission = TranslationPermission.OPEN
521+ self.assertTrue(self._doesPackageShare(package, by_maintainer=True))
522+ product.translationpermission = TranslationPermission.CLOSED
523+ self.assertTrue(self._doesPackageShare(package, by_maintainer=True))
524+
525+ def test_distribution_shares_only_by_maintainer_without_upstream(self):
526+ # Without an upstream product series, translations will only be
527+ # shared if they do originate from uploads done by the maintainer.
528 package = self.factory.makeSourcePackage()
529- distro = package.distroseries.distribution
530- for from_upstream in [False, True]:
531+ for by_maintainer in [False, True]:
532 self.assertEqual(
533- from_upstream, self._doesPackageShare(package, from_upstream))
534+ by_maintainer, self._doesPackageShare(package, by_maintainer))
535
536=== modified file 'lib/lp/translations/utilities/tests/test_file_importer.py'
537--- lib/lp/translations/utilities/tests/test_file_importer.py 2011-01-24 15:51:18 +0000
538+++ lib/lp/translations/utilities/tests/test_file_importer.py 2011-02-11 14:44:33 +0000
539@@ -11,7 +11,6 @@
540 from zope.component import getUtility
541 from zope.security.proxy import removeSecurityProxy
542
543-from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
544 from canonical.librarian.testing.fake import FakeLibrarian
545 from canonical.testing import (
546 LaunchpadZopelessLayer,
547@@ -21,6 +20,7 @@
548 from lp.testing import TestCaseWithFactory
549 from lp.translations.enums import TranslationPermission
550 from lp.translations.interfaces.potemplate import IPOTemplateSet
551+from lp.translations.interfaces.side import TranslationSide
552 from lp.translations.interfaces.translationfileformat import (
553 TranslationFileFormat,
554 )
555@@ -582,9 +582,6 @@
556 """Class test for the sharing operation of the FileImporter base class."""
557 layer = LaunchpadZopelessLayer
558
559- UPSTREAM = 0
560- UBUNTU = 1
561-
562 POFILE = dedent("""\
563 msgid ""
564 msgstr ""
565@@ -612,18 +609,15 @@
566
567 def _makeImportEntry(self, side, by_maintainer=False, uploader=None,
568 no_upstream=False):
569- if side == self.UPSTREAM:
570+ if side == TranslationSide.UPSTREAM:
571 potemplate = self.upstream_template
572 else:
573 # Create a template in a source package.
574- ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
575- distroseries = self.factory.makeDistroSeries(distribution=ubuntu)
576- ubuntu.translation_focus = distroseries
577- sourcepackagename = self.factory.makeSourcePackageName()
578 potemplate = self.factory.makePOTemplate(
579- distroseries=distroseries,
580- sourcepackagename=sourcepackagename,
581- name=self.upstream_template.name)
582+ name=self.upstream_template.name, side=side)
583+ distroseries = potemplate.distroseries
584+ sourcepackagename = potemplate.sourcepackagename
585+ distroseries.distribution.translation_focus = distroseries
586 if not no_upstream:
587 # Link the source package to the upstream series to
588 # enable sharing.
589@@ -657,7 +651,7 @@
590
591 def test_makeImportEntry_templates_are_sharing(self):
592 # Sharing between upstream and Ubuntu was set up correctly.
593- entry = self._makeImportEntry(self.UBUNTU)
594+ entry = self._makeImportEntry(TranslationSide.UBUNTU)
595 subset = getUtility(IPOTemplateSet).getSharingSubset(
596 distribution=entry.distroseries.distribution,
597 sourcepackagename=entry.sourcepackagename)
598@@ -667,7 +661,7 @@
599
600 def test_share_with_other_side_upstream(self):
601 # An upstream queue entry will be shared with ubuntu.
602- entry = self._makeImportEntry(self.UPSTREAM)
603+ entry = self._makeImportEntry(TranslationSide.UPSTREAM)
604 importer = POFileImporter(
605 entry, importers[TranslationFileFormat.PO], None)
606 self.assertTrue(
607@@ -676,7 +670,7 @@
608
609 def test_share_with_other_side_ubuntu(self):
610 # An ubuntu queue entry will not be shared with upstream.
611- entry = self._makeImportEntry(self.UBUNTU)
612+ entry = self._makeImportEntry(TranslationSide.UBUNTU)
613 importer = POFileImporter(
614 entry, importers[TranslationFileFormat.PO], None)
615 self.assertFalse(
616@@ -685,7 +679,8 @@
617
618 def test_share_with_other_side_ubuntu_no_upstream(self):
619 # An ubuntu queue entry cannot share with a non-existent upstream.
620- entry = self._makeImportEntry(self.UBUNTU, no_upstream=True)
621+ entry = self._makeImportEntry(
622+ TranslationSide.UBUNTU, no_upstream=True)
623 importer = POFileImporter(
624 entry, importers[TranslationFileFormat.PO], None)
625 self.assertFalse(
626@@ -696,9 +691,75 @@
627 # If the uploader in ubuntu has rights on upstream as well, the
628 # translations are shared.
629 entry = self._makeImportEntry(
630- self.UBUNTU, uploader=self.translator.translator)
631+ TranslationSide.UBUNTU, uploader=self.translator.translator)
632 importer = POFileImporter(
633 entry, importers[TranslationFileFormat.PO], None)
634 self.assertTrue(
635 importer.share_with_other_side,
636 "Ubuntu import should share with upstream.")
637+
638+ def test_is_upstream_import_on_sourcepackage_none(self):
639+ # To do an upstream import on a sourcepackage, three conditions must
640+ # be met.
641+ # - It has to be on a sourcepackage.
642+ # - The by_maintainer flag must be set on the queue entry.
643+ # - There must be no matching template in the upstream project or
644+ # even no upstream project at all.
645+ # This case meets none of them.
646+ entry = self._makeImportEntry(
647+ TranslationSide.UPSTREAM, uploader=self.translator.translator)
648+ importer = POFileImporter(
649+ entry, importers[TranslationFileFormat.PO], None)
650+ self.assertFalse(importer.is_upstream_import_on_sourcepackage)
651+
652+ def test_is_upstream_import_on_sourcepackage_by_maintainer(self):
653+ # This entry is by_maintainer.
654+ entry = self._makeImportEntry(
655+ TranslationSide.UPSTREAM, by_maintainer=True,
656+ uploader=self.translator.translator)
657+ importer = POFileImporter(
658+ entry, importers[TranslationFileFormat.PO], None)
659+ self.assertFalse(importer.is_upstream_import_on_sourcepackage)
660+
661+ def test_is_upstream_import_on_sourcepackage_upstream_template(self):
662+ # This entry is for a sourcepackage with an upstream potemplate.
663+ entry = self._makeImportEntry(
664+ TranslationSide.UBUNTU, uploader=self.translator.translator)
665+ importer = POFileImporter(
666+ entry, importers[TranslationFileFormat.PO], None)
667+ self.assertFalse(importer.is_upstream_import_on_sourcepackage)
668+
669+ def test_is_upstream_import_on_sourcepackage_upstream_any_template(self):
670+ # Actually any upstream potemplate will disallow upstream imports.
671+
672+ # Use _makeImportEntry to create upstream template and packaging
673+ # link.
674+ unused_entry = self._makeImportEntry(
675+ TranslationSide.UBUNTU, uploader=self.translator.translator)
676+
677+ sourcepackagename = unused_entry.sourcepackagename
678+ distroseries = unused_entry.distroseries
679+ other_potemplate = self.factory.makePOTemplate(
680+ distroseries=distroseries, sourcepackagename=sourcepackagename)
681+
682+ entry = self.factory.makeTranslationImportQueueEntry(
683+ potemplate=other_potemplate, by_maintainer=True,
684+ uploader=self.translator.translator, content=self.POFILE)
685+ entry.potemplate = other_potemplate
686+ entry.pofile = self.factory.makePOFile(potemplate=other_potemplate)
687+ transaction.commit()
688+
689+ importer = POFileImporter(
690+ entry, importers[TranslationFileFormat.PO], None)
691+
692+ self.assertFalse(importer.is_upstream_import_on_sourcepackage)
693+
694+ def test_is_upstream_import_on_sourcepackage_ok(self):
695+ # This entry qualifies.
696+ entry = self._makeImportEntry(
697+ TranslationSide.UBUNTU, by_maintainer=True, no_upstream=True,
698+ uploader=self.translator.translator)
699+ importer = POFileImporter(
700+ entry, importers[TranslationFileFormat.PO], None)
701+ self.assertTrue(importer.is_upstream_import_on_sourcepackage)
702+
703
704=== modified file 'lib/lp/translations/utilities/tests/test_translation_sharing_info.py'
705--- lib/lp/translations/utilities/tests/test_translation_sharing_info.py 2011-02-03 18:40:36 +0000
706+++ lib/lp/translations/utilities/tests/test_translation_sharing_info.py 2011-02-11 14:44:33 +0000
707@@ -264,6 +264,27 @@
708 has_upstream_template(
709 distroseries, sourcepackagename, different_templatename))
710
711+ def test_has_upstream_template_any_template(self):
712+ # There is one template on the upstream project, not specifying
713+ # a template name still indicates that there is a template.
714+ distroseries, sourcepackagename = self._makeSourcePackage()
715+ productseries = self._makeUpstreamProductSeries(
716+ distroseries, sourcepackagename)
717+ self.factory.makePOTemplate(
718+ productseries=productseries)
719+
720+ self.assertTrue(
721+ has_upstream_template(distroseries, sourcepackagename))
722+
723+ def test_has_upstream_template_any_template_none(self):
724+ # There is no template on the upstream project.
725+ distroseries, sourcepackagename = self._makeSourcePackage()
726+ productseries = self._makeUpstreamProductSeries(
727+ distroseries, sourcepackagename)
728+
729+ self.assertFalse(
730+ has_upstream_template(distroseries, sourcepackagename))
731+
732 def test_has_ubuntu_template_no_sourcepackage(self):
733 # There is no Ubuntu source package, so no Ubuntu template can be
734 # found.
735@@ -308,3 +329,22 @@
736
737 self.assertFalse(
738 has_ubuntu_template(productseries, different_templatename))
739+
740+ def test_has_ubuntu_template_any_template(self):
741+ # There is one template on the Ubuntu source package, not specifying
742+ # a template name still indicates that there is a template.
743+ distroseries, sourcepackagename = self._makeSourcePackage()
744+ productseries = self._makeUpstreamProductSeries(
745+ distroseries, sourcepackagename)
746+ self.factory.makePOTemplate(
747+ distroseries=distroseries, sourcepackagename=sourcepackagename)
748+
749+ self.assertTrue(has_ubuntu_template(productseries))
750+
751+ def test_has_ubuntu_template_any_template_none(self):
752+ # There is no template on the Ubuntu source package.
753+ distroseries, sourcepackagename = self._makeSourcePackage()
754+ productseries = self._makeUpstreamProductSeries(
755+ distroseries, sourcepackagename)
756+
757+ self.assertFalse(has_ubuntu_template(productseries))
758
759=== modified file 'lib/lp/translations/utilities/translation_import.py'
760--- lib/lp/translations/utilities/translation_import.py 2011-01-04 17:23:42 +0000
761+++ lib/lp/translations/utilities/translation_import.py 2011-02-11 14:44:33 +0000
762@@ -432,6 +432,23 @@
763 purportedly_upstream=from_upstream)
764
765 @cachedproperty
766+ def is_upstream_import_on_sourcepackage(self):
767+ """Use TranslationMessage.acceptFromUpstreamImportOnPackage`."""
768+ if self.pofile is None:
769+ return False
770+ if not self.translation_import_queue_entry.by_maintainer:
771+ return False
772+ if self.translation_import_queue_entry.sourcepackagename is None:
773+ return False
774+ distroseries = self.translation_import_queue_entry.distroseries
775+ sourcepackagename = (
776+ self.translation_import_queue_entry.sourcepackagename)
777+ from lp.translations.utilities.translationsharinginfo import (
778+ has_upstream_template)
779+ return not has_upstream_template(
780+ distroseries=distroseries, sourcepackagename=sourcepackagename)
781+
782+ @cachedproperty
783 def translations_are_msgids(self):
784 """Are these English strings instead of translations?
785
786@@ -466,12 +483,16 @@
787 message.validation_status = TranslationValidationStatus.OK
788 return True
789
790- def _approveMessage(self, potmsgset, message, message_data):
791+ def _acceptMessage(self, potmsgset, message, message_data):
792 """Try to approve the message, return None on TranslationConflict."""
793 try:
794- message.approve(
795- self.pofile, self.last_translator,
796- self.share_with_other_side, self.lock_timestamp)
797+ if self.is_upstream_import_on_sourcepackage:
798+ message.acceptFromUpstreamImportOnPackage(
799+ self.pofile, self.lock_timestamp)
800+ else:
801+ message.acceptFromImport(
802+ self.pofile, self.share_with_other_side,
803+ self.lock_timestamp)
804 except TranslationConflict:
805 self._addConflictError(message_data, potmsgset)
806 if self.logger is not None:
807@@ -529,7 +550,7 @@
808 validation_ok = self._validateMessage(
809 potmsgset, new_message, sanitized_translations, message_data)
810 if validation_ok and self.is_editor:
811- return self._approveMessage(potmsgset, new_message, message_data)
812+ return self._acceptMessage(potmsgset, new_message, message_data)
813
814 return new_message
815
816
817=== modified file 'lib/lp/translations/utilities/translationsharinginfo.py'
818--- lib/lp/translations/utilities/translationsharinginfo.py 2011-02-03 18:40:36 +0000
819+++ lib/lp/translations/utilities/translationsharinginfo.py 2011-02-11 14:44:33 +0000
820@@ -161,14 +161,14 @@
821 distroseries, sourcepackagename, templatename))
822
823
824-def has_ubuntu_template(productseries, templatename):
825+def has_ubuntu_template(productseries, templatename=None):
826 """Check for existence of ubuntu template."""
827 result = find_ubuntu_sharing_info(
828 productseries, templatename, template_only=True)
829 return not result.is_empty()
830
831
832-def has_upstream_template(distroseries, sourcepackagename, templatename):
833+def has_upstream_template(distroseries, sourcepackagename, templatename=None):
834 """Check for existence of upstream template."""
835 result = find_upstream_sharing_info(
836 distroseries, sourcepackagename, templatename,
837
838=== added file 'scripts/rosetta/upload-translations.py'
839--- scripts/rosetta/upload-translations.py 1970-01-01 00:00:00 +0000
840+++ scripts/rosetta/upload-translations.py 2011-02-11 14:44:33 +0000
841@@ -0,0 +1,19 @@
842+#!/usr/bin/python -S
843+#
844+# Copyright 2009 Canonical Ltd. This software is licensed under the
845+# GNU Affero General Public License version 3 (see the file LICENSE).
846+# pylint: disable-msg=W0403
847+
848+"""Upload translations to given package."""
849+
850+__metaclass__ = type
851+
852+import _pythonpath
853+
854+from lp.translations.scripts.upload_translations import (
855+ UploadPackageTranslations)
856+
857+
858+if __name__ == '__main__':
859+ script = UploadPackageTranslations('upload-translations')
860+ script.run()