Merge lp:~danilo/launchpad/translatedlanguage into lp:launchpad/db-devel

Proposed by Данило Шеган
Status: Rejected
Rejected by: Данило Шеган
Proposed branch: lp:~danilo/launchpad/translatedlanguage
Merge into: lp:launchpad/db-devel
Prerequisite: lp:~danilo/launchpad/pass-language-to-getdummy
Diff against target: 2258 lines (+1076/-208) (has conflicts)
39 files modified
lib/lp/code/browser/tests/test_branchlisting.py (+4/-2)
lib/lp/code/browser/tests/test_sourcepackagerecipe.py (+12/-5)
lib/lp/code/browser/tests/test_sourcepackagerecipebuild.py (+4/-1)
lib/lp/code/model/tests/test_branch.py (+4/-1)
lib/lp/code/model/tests/test_linkedbranch.py (+7/-2)
lib/lp/code/model/tests/test_sourcepackagerecipebuild.py (+5/-2)
lib/lp/registry/browser/tests/distroseries-views.txt (+1/-1)
lib/lp/registry/browser/tests/milestone-views.txt (+5/-1)
lib/lp/registry/browser/tests/productseries-views.txt (+6/-2)
lib/lp/registry/model/person.py (+1/-1)
lib/lp/registry/model/productseries.py (+4/-4)
lib/lp/registry/stories/webservice/xx-project-registry.txt (+4/-2)
lib/lp/registry/tests/test_distroseries.py (+15/-6)
lib/lp/registry/tests/test_sourcepackage.py (+9/-2)
lib/lp/soyuz/adapters/tests/test_packagelocation.py (+4/-1)
lib/lp/soyuz/browser/tests/archive-views.txt (+7/-2)
lib/lp/soyuz/browser/tests/test_distrosourcepackagerelease.py (+7/-1)
lib/lp/soyuz/browser/tests/test_sourcepackagerelease.py (+11/-5)
lib/lp/soyuz/doc/archive.txt (+9/-4)
lib/lp/soyuz/stories/soyuz/xx-distribution-archives.txt (+12/-5)
lib/lp/soyuz/tests/test_publishing.py (+9/-3)
lib/lp/testing/factory.py (+87/-11)
lib/lp/testing/tests/test_factory.py (+50/-0)
lib/lp/translations/browser/configure.zcml (+1/-1)
lib/lp/translations/browser/serieslanguage.py (+11/-8)
lib/lp/translations/browser/tests/test_breadcrumbs.py (+3/-1)
lib/lp/translations/configure.zcml (+17/-0)
lib/lp/translations/doc/translations-export-to-branch.txt (+5/-1)
lib/lp/translations/interfaces/potemplate.py (+14/-0)
lib/lp/translations/interfaces/productserieslanguage.py (+4/-29)
lib/lp/translations/interfaces/translatedlanguage.py (+79/-0)
lib/lp/translations/model/potemplate.py (+10/-3)
lib/lp/translations/model/productserieslanguage.py (+20/-83)
lib/lp/translations/model/translatedlanguage.py (+132/-0)
lib/lp/translations/stories/buildfarm/xx-build-summary.txt (+8/-3)
lib/lp/translations/tests/test_productserieslanguage.py (+10/-11)
lib/lp/translations/tests/test_translatedlanguage.py (+462/-0)
lib/lp/translations/tests/test_translationtemplatescollection.py (+19/-0)
utilities/make-lp-user (+4/-4)
Text conflict in lib/lp/code/interfaces/branch.py
To merge this branch: bzr merge lp:~danilo/launchpad/translatedlanguage
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Needs Resubmitting
Review via email: mp+30760@code.launchpad.net

Commit message

Provide ITranslatedLanguage interface along with a TranslatedLanguageMixin and use that for ProductSeriesLanguage implementation.

Description of the change

= ITranslatedLanguage =

This provides a generic ITranslatedLanguage interface for objects which are a translation of something (i.e. a productseries, distroseries, sourcepackage, template) into a single language, along with a mixin that implements this interface in a generic way.

Mixin is to replace most of the model code on DistroSeriesLanguage and ProductSeriesLanguage, and to be the basis of cleaning up SourcePackageTranslations. It relies on the "parent" object implementing IHasTranslationTemplates with its getCurrentTemplatesCollection() method.

The next steps would be to switch ProductSeriesLanguage, DistroSeriesLanguage and SourcePackageTranslations to make the most of the mixin, but that would result in a huge branch (as if this one isn't big already). So, we only migrate ProductSeriesLanguage in this one (other than a few display-orienteed attributes, the only bits that remain in it are IRosettaStats methods which we want to get rid of as well).

As a preparation for getting rid of IRosettaStats, I introduce a temporary statistics object implementation (a dict) which we want to switch everything to (a better one is in progress in one of Adi's branches).

The most interesting bit of the code is inside the mixin: POFilesByPOTemplates is an iterator-like object which allows slicing over a full set of POTemplates regardless of the presense of POFiles (when they are missing, we return DummyPOFile objects). This ensures we do a constant number of queries for every request.

Unfortunately, for listifying TranslatedLanguageMixin.pofiles when __len__ is defined on POFilesByPOTemplates (a requirement for BatchNavigator), it's always called even if using an iterator would be enough: this means 2 queries instead of 1. However, slicing always does a single query, as confirmed in the test.

It is (somewhat) indirectly unit-tested inside the TranslationTemplateMixinTest, though that's simply because 'pofiles' attribute implementation in the mixin is basically a set-up of POFilesByPOTemplates.

Full test is otherwise written in a way to make it easy to extend for testing over different types of objects implementing ITranslatedLanguage, even though it only tests ProductSeriesLanguage now.

= Tests =

 bin/test -cvvt test_translatedlanguage -t serieslanguage

= Demo & QA =

A few examples:

 https://translations.launchpad.dev/evolution/trunk/+lang/es
 https://translations.launchpad.dev/evolution/trunk/+lang/es?batch=1
 https://translations.launchpad.dev/evolution/trunk/+lang/sr (no PO files)
 https://translations.launchpad.dev/evolution/trunk/+lang/sr?batch=1

And to confirm we haven't broken DistroSeriesLanguage pages:

 https://translations.launchpad.dev/ubuntu/hoary/+lang/es
 https://translations.launchpad.dev/ubuntu/hoary/+lang/es

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/registry/model/productseries.py
  lib/lp/testing/factory.py
  lib/lp/translations/browser/configure.zcml
  lib/lp/translations/browser/serieslanguage.py
  lib/lp/translations/configure.zcml
  lib/lp/translations/interfaces/potemplate.py
  lib/lp/translations/interfaces/productserieslanguage.py
  lib/lp/translations/interfaces/translatedlanguage.py
  lib/lp/translations/model/potemplate.py
  lib/lp/translations/model/productserieslanguage.py
  lib/lp/translations/model/translatedlanguage.py
  lib/lp/translations/tests/test_productserieslanguage.py
  lib/lp/translations/tests/test_translatedlanguage.py
  lib/lp/translations/tests/test_translationtemplatescollection.py

./lib/lp/translations/interfaces/potemplate.py
     736: E301 expected 1 blank line, found 2
     750: E301 expected 1 blank line, found 2
     784: E302 expected 2 blank lines, found 1
    1312: E202 whitespace before ']'
    1400: E202 whitespace before ']'
    1407: E202 whitespace before ']'
    1510: E202 whitespace before ']'

(E301 happens due to comments in interface definition, E202 because of multi-line list definitions; I am not changing these for now, though I did fix a bunch of lint issues; also, these are across two different files: interfaces/potemplate.py and model/potemplate.py, but linter is very buggy)

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Was this really meant to be proposed for db-devel? I'm recognizing changes from other people's branches that I reviewed this week in the diff.

review: Needs Resubmitting
Revision history for this message
Данило Шеган (danilo) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/code/browser/tests/test_branchlisting.py'
--- lib/lp/code/browser/tests/test_branchlisting.py 2010-05-28 09:54:45 +0000
+++ lib/lp/code/browser/tests/test_branchlisting.py 2010-07-23 12:49:25 +0000
@@ -32,12 +32,14 @@
32from lp.testing import (32from lp.testing import (
33 BrowserTestCase, TestCase, TestCaseWithFactory, login_person,33 BrowserTestCase, TestCase, TestCaseWithFactory, login_person,
34 person_logged_in, time_counter)34 person_logged_in, time_counter)
35from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
35from lp.testing.views import create_initialized_view36from lp.testing.views import create_initialized_view
36from canonical.launchpad.testing.pages import extract_text, find_tag_by_id37from canonical.launchpad.testing.pages import extract_text, find_tag_by_id
37from canonical.launchpad.webapp import canonical_url38from canonical.launchpad.webapp import canonical_url
38from canonical.launchpad.webapp.servers import LaunchpadTestRequest39from canonical.launchpad.webapp.servers import LaunchpadTestRequest
39from canonical.testing.layers import DatabaseFunctionalLayer40from canonical.testing.layers import DatabaseFunctionalLayer
4041
42
41class TestListingToSortOrder(TestCase):43class TestListingToSortOrder(TestCase):
42 """Tests for the BranchSet._listingSortToOrderBy static method.44 """Tests for the BranchSet._listingSortToOrderBy static method.
4345
@@ -60,6 +62,7 @@
6062
61 def assertSortsEqual(self, sort_one, sort_two):63 def assertSortsEqual(self, sort_one, sort_two):
62 """Assert that one list of sort specs is equal to another."""64 """Assert that one list of sort specs is equal to another."""
65
63 def sort_data(sort):66 def sort_data(sort):
64 return sort.suffix, sort.expr67 return sort.suffix, sort.expr
65 self.assertEqual(map(sort_data, sort_one), map(sort_data, sort_two))68 self.assertEqual(map(sort_data, sort_one), map(sort_data, sort_two))
@@ -352,7 +355,7 @@
352 # series on the main site, not the code site.355 # series on the main site, not the code site.
353 branch = self.factory.makeProductBranch()356 branch = self.factory.makeProductBranch()
354 series = self.factory.makeProductSeries(product=branch.product)357 series = self.factory.makeProductSeries(product=branch.product)
355 series.branch = branch358 remove_security_proxy_and_shout_at_engineer(series).branch = branch
356 browser = self.getUserBrowser(359 browser = self.getUserBrowser(
357 canonical_url(branch.product, rootsite='code'))360 canonical_url(branch.product, rootsite='code'))
358 link = browser.getLink(re.compile('^' + series.name + '$'))361 link = browser.getLink(re.compile('^' + series.name + '$'))
@@ -402,4 +405,3 @@
402405
403def test_suite():406def test_suite():
404 return unittest.TestLoader().loadTestsFromName(__name__)407 return unittest.TestLoader().loadTestsFromName(__name__)
405
406408
=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-07-21 21:16:54 +0000
+++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-07-23 12:49:25 +0000
@@ -29,6 +29,7 @@
29from lp.registry.interfaces.pocket import PackagePublishingPocket29from lp.registry.interfaces.pocket import PackagePublishingPocket
30from lp.soyuz.model.processor import ProcessorFamily30from lp.soyuz.model.processor import ProcessorFamily
31from lp.testing import ANONYMOUS, BrowserTestCase, login, logout31from lp.testing import ANONYMOUS, BrowserTestCase, login, logout
32from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
3233
3334
34class TestCaseForRecipe(BrowserTestCase):35class TestCaseForRecipe(BrowserTestCase):
@@ -45,7 +46,9 @@
45 self.squirrel = self.factory.makeDistroSeries(46 self.squirrel = self.factory.makeDistroSeries(
46 displayname='Secret Squirrel', name='secret', version='100.04',47 displayname='Secret Squirrel', name='secret', version='100.04',
47 distribution=self.ppa.distribution)48 distribution=self.ppa.distribution)
48 self.squirrel.nominatedarchindep = self.squirrel.newArch(49 naked_squirrel = remove_security_proxy_and_shout_at_engineer(
50 self.squirrel)
51 naked_squirrel.nominatedarchindep = self.squirrel.newArch(
49 'i386', ProcessorFamily.get(1), False, self.chef,52 'i386', ProcessorFamily.get(1), False, self.chef,
50 supports_virtualized=True)53 supports_virtualized=True)
5154
@@ -494,7 +497,7 @@
494 def makeBuildJob(self, recipe):497 def makeBuildJob(self, recipe):
495 """Return a build associated with a buildjob."""498 """Return a build associated with a buildjob."""
496 build = self.factory.makeSourcePackageRecipeBuild(499 build = self.factory.makeSourcePackageRecipeBuild(
497 recipe=recipe, distroseries=self.squirrel, archive=self.ppa )500 recipe=recipe, distroseries=self.squirrel, archive=self.ppa)
498 self.factory.makeSourcePackageRecipeBuildJob(recipe_build=build)501 self.factory.makeSourcePackageRecipeBuildJob(recipe_build=build)
499 return build502 return build
500503
@@ -527,6 +530,7 @@
527 self.assertEqual(530 self.assertEqual(
528 set([build1, build2, build3, build4, build5, build6]),531 set([build1, build2, build3, build4, build5, build6]),
529 set(view.builds))532 set(view.builds))
533
530 def set_day(build, day):534 def set_day(build, day):
531 removeSecurityProxy(build).datebuilt = datetime(535 removeSecurityProxy(build).datebuilt = datetime(
532 2010, 03, day, tzinfo=utc)536 2010, 03, day, tzinfo=utc)
@@ -569,7 +573,8 @@
569 woody = self.factory.makeDistroSeries(573 woody = self.factory.makeDistroSeries(
570 name='woody', displayname='Woody',574 name='woody', displayname='Woody',
571 distribution=self.ppa.distribution)575 distribution=self.ppa.distribution)
572 woody.nominatedarchindep = woody.newArch(576 naked_woody = remove_security_proxy_and_shout_at_engineer(woody)
577 naked_woody.nominatedarchindep = woody.newArch(
573 'i386', ProcessorFamily.get(1), False, self.factory.makePerson(),578 'i386', ProcessorFamily.get(1), False, self.factory.makePerson(),
574 supports_virtualized=True)579 supports_virtualized=True)
575580
@@ -603,7 +608,8 @@
603 woody = self.factory.makeDistroSeries(608 woody = self.factory.makeDistroSeries(
604 name='woody', displayname='Woody',609 name='woody', displayname='Woody',
605 distribution=self.ppa.distribution)610 distribution=self.ppa.distribution)
606 woody.nominatedarchindep = woody.newArch(611 naked_woody = remove_security_proxy_and_shout_at_engineer(woody)
612 naked_woody.nominatedarchindep = woody.newArch(
607 'i386', ProcessorFamily.get(1), False, self.factory.makePerson(),613 'i386', ProcessorFamily.get(1), False, self.factory.makePerson(),
608 supports_virtualized=True)614 supports_virtualized=True)
609615
@@ -624,7 +630,8 @@
624 woody = self.factory.makeDistroSeries(630 woody = self.factory.makeDistroSeries(
625 name='woody', displayname='Woody',631 name='woody', displayname='Woody',
626 distribution=self.ppa.distribution)632 distribution=self.ppa.distribution)
627 woody.nominatedarchindep = woody.newArch(633 naked_woody = remove_security_proxy_and_shout_at_engineer(woody)
634 naked_woody.nominatedarchindep = woody.newArch(
628 'i386', ProcessorFamily.get(1), False, self.factory.makePerson(),635 'i386', ProcessorFamily.get(1), False, self.factory.makePerson(),
629 supports_virtualized=True)636 supports_virtualized=True)
630637
631638
=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipebuild.py'
--- lib/lp/code/browser/tests/test_sourcepackagerecipebuild.py 2010-07-19 09:22:06 +0000
+++ lib/lp/code/browser/tests/test_sourcepackagerecipebuild.py 2010-07-23 12:49:25 +0000
@@ -18,6 +18,7 @@
18from lp.buildmaster.interfaces.buildbase import BuildStatus18from lp.buildmaster.interfaces.buildbase import BuildStatus
19from lp.soyuz.model.processor import ProcessorFamily19from lp.soyuz.model.processor import ProcessorFamily
20from lp.testing import ANONYMOUS, BrowserTestCase, login, logout20from lp.testing import ANONYMOUS, BrowserTestCase, login, logout
21from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
2122
2223
23class TestSourcePackageRecipeBuild(BrowserTestCase):24class TestSourcePackageRecipeBuild(BrowserTestCase):
@@ -36,7 +37,9 @@
36 self.squirrel = self.factory.makeDistroSeries(37 self.squirrel = self.factory.makeDistroSeries(
37 displayname='Secret Squirrel', name='secret', version='100.04',38 displayname='Secret Squirrel', name='secret', version='100.04',
38 distribution=self.ppa.distribution)39 distribution=self.ppa.distribution)
39 self.squirrel.nominatedarchindep = self.squirrel.newArch(40 naked_squirrel = remove_security_proxy_and_shout_at_engineer(
41 self.squirrel)
42 naked_squirrel.nominatedarchindep = self.squirrel.newArch(
40 'i386', ProcessorFamily.get(1), False, self.chef,43 'i386', ProcessorFamily.get(1), False, self.chef,
41 supports_virtualized=True)44 supports_virtualized=True)
4245
4346
=== modified file 'lib/lp/code/model/tests/test_branch.py'
--- lib/lp/code/model/tests/test_branch.py 2010-07-17 23:15:30 +0000
+++ lib/lp/code/model/tests/test_branch.py 2010-07-23 12:49:25 +0000
@@ -537,7 +537,7 @@
537 jobs = list(getUtility(IBranchUpgradeJobSource).iterReady())537 jobs = list(getUtility(IBranchUpgradeJobSource).iterReady())
538 self.assertEqual(538 self.assertEqual(
539 jobs,539 jobs,
540 [job,])540 [job, ])
541541
542 def test_requestUpgrade_no_upgrade_needed(self):542 def test_requestUpgrade_no_upgrade_needed(self):
543 # If a branch doesn't need to be upgraded, requestUpgrade raises an543 # If a branch doesn't need to be upgraded, requestUpgrade raises an
@@ -635,6 +635,7 @@
635 branch = self.factory.makeProductBranch(635 branch = self.factory.makeProductBranch(
636 product=fooix, owner=eric, name='trunk')636 product=fooix, owner=eric, name='trunk')
637 linked_branch = ICanHasLinkedBranch(future)637 linked_branch = ICanHasLinkedBranch(future)
638 login_person(fooix.owner)
638 linked_branch.setBranch(branch)639 linked_branch.setBranch(branch)
639 self.assertEqual(640 self.assertEqual(
640 [linked_branch],641 [linked_branch],
@@ -827,6 +828,7 @@
827 product = branch.product828 product = branch.product
828 series = self.factory.makeProductSeries(product=product)829 series = self.factory.makeProductSeries(product=product)
829 linked_branch = ICanHasLinkedBranch(series)830 linked_branch = ICanHasLinkedBranch(series)
831 login_person(series.owner)
830 linked_branch.setBranch(branch)832 linked_branch.setBranch(branch)
831 self.assertBzrIdentity(branch, linked_branch.bzr_path)833 self.assertBzrIdentity(branch, linked_branch.bzr_path)
832834
@@ -852,6 +854,7 @@
852 removeSecurityProxy(branch.product))854 removeSecurityProxy(branch.product))
853 series_link = ICanHasLinkedBranch(series)855 series_link = ICanHasLinkedBranch(series)
854 product_link.setBranch(branch)856 product_link.setBranch(branch)
857 login_person(series.owner)
855 series_link.setBranch(branch)858 series_link.setBranch(branch)
856 self.assertBzrIdentity(branch, product_link.bzr_path)859 self.assertBzrIdentity(branch, product_link.bzr_path)
857860
858861
=== modified file 'lib/lp/code/model/tests/test_linkedbranch.py'
--- lib/lp/code/model/tests/test_linkedbranch.py 2010-04-16 15:06:55 +0000
+++ lib/lp/code/model/tests/test_linkedbranch.py 2010-07-23 12:49:25 +0000
@@ -18,6 +18,7 @@
18from lp.registry.interfaces.distroseries import NoSuchDistroSeries18from lp.registry.interfaces.distroseries import NoSuchDistroSeries
19from lp.registry.interfaces.pocket import PackagePublishingPocket19from lp.registry.interfaces.pocket import PackagePublishingPocket
20from lp.testing import run_with_login, TestCaseWithFactory20from lp.testing import run_with_login, TestCaseWithFactory
21from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
2122
2223
23class TestProductSeriesLinkedBranch(TestCaseWithFactory):24class TestProductSeriesLinkedBranch(TestCaseWithFactory):
@@ -27,7 +28,9 @@
27 def test_branch(self):28 def test_branch(self):
28 # The linked branch of a product series is its branch attribute.29 # The linked branch of a product series is its branch attribute.
29 product_series = self.factory.makeProductSeries()30 product_series = self.factory.makeProductSeries()
30 product_series.branch = self.factory.makeProductBranch(31 naked_product_series = remove_security_proxy_and_shout_at_engineer(
32 product_series)
33 naked_product_series.branch = self.factory.makeProductBranch(
31 product=product_series.product)34 product=product_series.product)
32 self.assertEqual(35 self.assertEqual(
33 product_series.branch, ICanHasLinkedBranch(product_series).branch)36 product_series.branch, ICanHasLinkedBranch(product_series).branch)
@@ -35,9 +38,11 @@
35 def test_setBranch(self):38 def test_setBranch(self):
36 # setBranch sets the linked branch of the product series.39 # setBranch sets the linked branch of the product series.
37 product_series = self.factory.makeProductSeries()40 product_series = self.factory.makeProductSeries()
41 naked_product_series = remove_security_proxy_and_shout_at_engineer(
42 product_series)
38 branch = self.factory.makeProductBranch(43 branch = self.factory.makeProductBranch(
39 product=product_series.product)44 product=product_series.product)
40 ICanHasLinkedBranch(product_series).setBranch(branch)45 ICanHasLinkedBranch(naked_product_series).setBranch(branch)
41 self.assertEqual(branch, product_series.branch)46 self.assertEqual(branch, product_series.branch)
4247
43 def test_bzr_path(self):48 def test_bzr_path(self):
4449
=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipebuild.py'
--- lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2010-07-20 12:12:46 +0000
+++ lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2010-07-23 12:49:25 +0000
@@ -37,6 +37,7 @@
37from lp.soyuz.model.processor import ProcessorFamily37from lp.soyuz.model.processor import ProcessorFamily
38from lp.soyuz.tests.soyuzbuilddhelpers import WaitingSlave38from lp.soyuz.tests.soyuzbuilddhelpers import WaitingSlave
39from lp.testing import ANONYMOUS, login, person_logged_in, TestCaseWithFactory39from lp.testing import ANONYMOUS, login, person_logged_in, TestCaseWithFactory
40from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
40from lp.testing.fakemethod import FakeMethod41from lp.testing.fakemethod import FakeMethod
41from lp.testing.mail_helpers import pop_notifications42from lp.testing.mail_helpers import pop_notifications
4243
@@ -53,7 +54,9 @@
53 distroseries_i386 = distroseries.newArch(54 distroseries_i386 = distroseries.newArch(
54 'i386', ProcessorFamily.get(1), False, person,55 'i386', ProcessorFamily.get(1), False, person,
55 supports_virtualized=True)56 supports_virtualized=True)
56 distroseries.nominatedarchindep = distroseries_i38657 naked_distroseries = remove_security_proxy_and_shout_at_engineer(
58 distroseries)
59 naked_distroseries.nominatedarchindep = distroseries_i386
5760
58 return getUtility(ISourcePackageRecipeBuildSource).new(61 return getUtility(ISourcePackageRecipeBuildSource).new(
59 distroseries=distroseries,62 distroseries=distroseries,
@@ -320,7 +323,7 @@
320 removeSecurityProxy(build).buildstate = BuildStatus.FULLYBUILT323 removeSecurityProxy(build).buildstate = BuildStatus.FULLYBUILT
321 IStore(build).flush()324 IStore(build).flush()
322 build.notify()325 build.notify()
323 (message,) = pop_notifications()326 (message, ) = pop_notifications()
324 requester = build.requester327 requester = build.requester
325 requester_address = format_address(328 requester_address = format_address(
326 requester.displayname, requester.preferredemail.email)329 requester.displayname, requester.preferredemail.email)
327330
=== modified file 'lib/lp/registry/browser/tests/distroseries-views.txt'
--- lib/lp/registry/browser/tests/distroseries-views.txt 2010-05-24 22:04:19 +0000
+++ lib/lp/registry/browser/tests/distroseries-views.txt 2010-07-23 12:49:25 +0000
@@ -196,7 +196,7 @@
196 >>> [field.__name__ for field in view.form_fields]196 >>> [field.__name__ for field in view.form_fields]
197 ['displayname', 'title', 'summary', 'description', 'status']197 ['displayname', 'title', 'summary', 'description', 'status']
198198
199 >>> print view.widgets.get('status')._getFormValue()199 >>> print view.widgets.get('status')._getFormValue().title
200 Active Development200 Active Development
201201
202202
203203
=== modified file 'lib/lp/registry/browser/tests/milestone-views.txt'
--- lib/lp/registry/browser/tests/milestone-views.txt 2010-06-30 19:37:09 +0000
+++ lib/lp/registry/browser/tests/milestone-views.txt 2010-07-23 12:49:25 +0000
@@ -579,9 +579,13 @@
579The driver of a series that belongs to an `IDerivativeDistribution` is a579The driver of a series that belongs to an `IDerivativeDistribution` is a
580release manager and can create milestones.580release manager and can create milestones.
581581
582 >>> from lp.testing.factory import (
583 ... remove_security_proxy_and_shout_at_engineer)
582 >>> distroseries = factory.makeDistroRelease(name='pumpkin')584 >>> distroseries = factory.makeDistroRelease(name='pumpkin')
583 >>> driver = factory.makePerson(name='a-driver')585 >>> driver = factory.makePerson(name='a-driver')
584 >>> distroseries.driver = driver586 >>> naked_distroseries = remove_security_proxy_and_shout_at_engineer(
587 ... distroseries)
588 >>> naked_distroseries.driver = driver
585 >>> login_person(driver)589 >>> login_person(driver)
586590
587 >>> form = {591 >>> form = {
588592
=== modified file 'lib/lp/registry/browser/tests/productseries-views.txt'
--- lib/lp/registry/browser/tests/productseries-views.txt 2010-06-13 02:01:25 +0000
+++ lib/lp/registry/browser/tests/productseries-views.txt 2010-07-23 12:49:25 +0000
@@ -255,6 +255,8 @@
255255
256 >>> from datetime import datetime256 >>> from datetime import datetime
257 >>> from pytz import UTC257 >>> from pytz import UTC
258 >>> from lp.testing.factory import (
259 ... remove_security_proxy_and_shout_at_engineer)
258260
259 >>> product = factory.makeProduct(name="field", displayname='Field')261 >>> product = factory.makeProduct(name="field", displayname='Field')
260 >>> productseries = factory.makeProductSeries(262 >>> productseries = factory.makeProductSeries(
@@ -263,7 +265,9 @@
263265
264 # Hack the creation date for testing purposes.266 # Hack the creation date for testing purposes.
265 >>> test_date = datetime(2009, 05, 01, 19, 34, 24, tzinfo=UTC)267 >>> test_date = datetime(2009, 05, 01, 19, 34, 24, tzinfo=UTC)
266 >>> productseries.datecreated = test_date268 >>> naked_productseries = remove_security_proxy_and_shout_at_engineer(
269 ... productseries)
270 >>> naked_productseries.datecreated = test_date
267271
268Users without edit permission cannot access the view.272Users without edit permission cannot access the view.
269273
@@ -470,7 +474,7 @@
470474
471The series status is set to obsolete and the releasefileglob was set to None.475The series status is set to obsolete and the releasefileglob was set to None.
472476
473 >>> print productseries.status477 >>> print productseries.status.title
474 Obsolete478 Obsolete
475 >>> print productseries.releasefileglob479 >>> print productseries.releasefileglob
476 None480 None
477481
=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py 2010-07-22 05:11:59 +0000
+++ lib/lp/registry/model/person.py 2010-07-23 12:49:25 +0000
@@ -2495,7 +2495,7 @@
2495 creation_rationale, comment=comment)2495 creation_rationale, comment=comment)
2496 db_updated = True2496 db_updated = True
24972497
2498 return IPerson(account), db_updated2498 return IPerson(account), db_updated
24992499
2500 def newTeam(self, teamowner, name, displayname, teamdescription=None,2500 def newTeam(self, teamowner, name, displayname, teamdescription=None,
2501 subscriptionpolicy=TeamSubscriptionPolicy.MODERATED,2501 subscriptionpolicy=TeamSubscriptionPolicy.MODERATED,
25022502
=== modified file 'lib/lp/registry/model/productseries.py'
--- lib/lp/registry/model/productseries.py 2010-07-15 15:01:18 +0000
+++ lib/lp/registry/model/productseries.py 2010-07-23 12:49:25 +0000
@@ -469,8 +469,8 @@
469 pofile.currentCount(),469 pofile.currentCount(),
470 pofile.updatesCount(),470 pofile.updatesCount(),
471 pofile.rosettaCount(),471 pofile.rosettaCount(),
472 pofile.unreviewedCount(),472 pofile.unreviewedCount())
473 pofile.date_changed)473 psl.last_changed_date = pofile.date_changed
474 results.append(psl)474 results.append(psl)
475 else:475 else:
476 # If there is more than one template, do a single476 # If there is more than one template, do a single
@@ -512,8 +512,8 @@
512 for (language, imported, changed, new, unreviewed,512 for (language, imported, changed, new, unreviewed,
513 last_changed) in ordered_results:513 last_changed) in ordered_results:
514 psl = ProductSeriesLanguage(self, language)514 psl = ProductSeriesLanguage(self, language)
515 psl.setCounts(515 psl.setCounts(total, imported, changed, new, unreviewed)
516 total, imported, changed, new, unreviewed, last_changed)516 psl.last_changed_date = last_changed
517 results.append(psl)517 results.append(psl)
518518
519 return results519 return results
520520
=== modified file 'lib/lp/registry/stories/webservice/xx-project-registry.txt'
--- lib/lp/registry/stories/webservice/xx-project-registry.txt 2010-06-14 18:32:58 +0000
+++ lib/lp/registry/stories/webservice/xx-project-registry.txt 2010-07-23 12:49:25 +0000
@@ -847,15 +847,17 @@
847The entry for a project series is available at its canonical URL on the847The entry for a project series is available at its canonical URL on the
848virtual host.848virtual host.
849849
850 >>> from zope.security.proxy import removeSecurityProxy
850 >>> login('test@canonical.com')851 >>> login('test@canonical.com')
851 >>> babadoo_owner = factory.makePerson(name='babadoo-owner')852 >>> babadoo_owner = factory.makePerson(name='babadoo-owner')
852 >>> babadoo = factory.makeProduct(name='babadoo', owner=babadoo_owner)853 >>> babadoo = factory.makeProduct(name='babadoo', owner=babadoo_owner)
853 >>> foobadoo = factory.makeProductSeries(854 >>> foobadoo = factory.makeProductSeries(
854 ... product=babadoo, name='foobadoo', owner=babadoo_owner)855 ... product=babadoo, name='foobadoo', owner=babadoo_owner)
855 >>> foobadoo.summary = u'Foobadoo support for Babadoo'856 >>> removeSecurityProxy(foobadoo).summary = (
857 ... u'Foobadoo support for Babadoo')
856 >>> fooey = factory.makeAnyBranch(858 >>> fooey = factory.makeAnyBranch(
857 ... product=babadoo, name='fooey', owner=babadoo_owner)859 ... product=babadoo, name='fooey', owner=babadoo_owner)
858 >>> foobadoo.branch = fooey860 >>> removeSecurityProxy(foobadoo).branch = fooey
859 >>> logout()861 >>> logout()
860862
861 >>> babadoo_foobadoo = webservice.get('/babadoo/foobadoo').jsonBody()863 >>> babadoo_foobadoo = webservice.get('/babadoo/foobadoo').jsonBody()
862864
=== modified file 'lib/lp/registry/tests/test_distroseries.py'
--- lib/lp/registry/tests/test_distroseries.py 2010-06-23 23:23:28 +0000
+++ lib/lp/registry/tests/test_distroseries.py 2010-07-23 12:49:25 +0000
@@ -24,6 +24,7 @@
24 active_publishing_status, PackagePublishingStatus)24 active_publishing_status, PackagePublishingStatus)
25from lp.soyuz.model.processor import ProcessorFamilySet25from lp.soyuz.model.processor import ProcessorFamilySet
26from lp.testing import TestCase, TestCaseWithFactory26from lp.testing import TestCase, TestCaseWithFactory
27from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
27from lp.soyuz.tests.test_publishing import SoyuzTestPublisher28from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
28from lp.translations.interfaces.translations import (29from lp.translations.interfaces.translations import (
29 TranslationsBranchImportMode)30 TranslationsBranchImportMode)
@@ -285,7 +286,9 @@
285 self.linkPackage('hot')286 self.linkPackage('hot')
286 self.makeSeriesPackage('cold')287 self.makeSeriesPackage('cold')
287 product_series = self.linkPackage('cold')288 product_series = self.linkPackage('cold')
288 product_series.product.bugtraker = self.factory.makeBugTracker()289 naked_product_series = remove_security_proxy_and_shout_at_engineer(
290 product_series)
291 naked_product_series.product.bugtraker = self.factory.makeBugTracker()
289 packagings = self.series.getPrioritizedlPackagings()292 packagings = self.series.getPrioritizedlPackagings()
290 names = [packaging.sourcepackagename.name for packaging in packagings]293 names = [packaging.sourcepackagename.name for packaging in packagings]
291 expected = [u'hot', u'cold', u'linked']294 expected = [u'hot', u'cold', u'linked']
@@ -296,7 +299,9 @@
296 self.linkPackage('translatable')299 self.linkPackage('translatable')
297 self.makeSeriesPackage('withbranch')300 self.makeSeriesPackage('withbranch')
298 product_series = self.linkPackage('withbranch')301 product_series = self.linkPackage('withbranch')
299 product_series.branch = self.factory.makeBranch()302 naked_product_series = remove_security_proxy_and_shout_at_engineer(
303 product_series)
304 naked_product_series.branch = self.factory.makeBranch()
300 packagings = self.series.getPrioritizedlPackagings()305 packagings = self.series.getPrioritizedlPackagings()
301 names = [packaging.sourcepackagename.name for packaging in packagings]306 names = [packaging.sourcepackagename.name for packaging in packagings]
302 expected = [u'translatable', u'linked', u'withbranch']307 expected = [u'translatable', u'linked', u'withbranch']
@@ -308,8 +313,10 @@
308 self.linkPackage('translatable')313 self.linkPackage('translatable')
309 self.makeSeriesPackage('importabletranslatable')314 self.makeSeriesPackage('importabletranslatable')
310 product_series = self.linkPackage('importabletranslatable')315 product_series = self.linkPackage('importabletranslatable')
311 product_series.branch = self.factory.makeBranch()316 naked_product_series = remove_security_proxy_and_shout_at_engineer(
312 product_series.translations_autoimport_mode = (317 product_series)
318 naked_product_series.branch = self.factory.makeBranch()
319 naked_product_series.translations_autoimport_mode = (
313 TranslationsBranchImportMode.IMPORT_TEMPLATES)320 TranslationsBranchImportMode.IMPORT_TEMPLATES)
314 packagings = self.series.getPrioritizedlPackagings()321 packagings = self.series.getPrioritizedlPackagings()
315 names = [packaging.sourcepackagename.name for packaging in packagings]322 names = [packaging.sourcepackagename.name for packaging in packagings]
@@ -343,7 +350,9 @@
343350
344 new_distroseries = (351 new_distroseries = (
345 self.factory.makeDistroRelease(name=u"sampleseries"))352 self.factory.makeDistroRelease(name=u"sampleseries"))
346 new_distroseries.hide_all_translations = False353 naked_new_distroseries = remove_security_proxy_and_shout_at_engineer(
354 new_distroseries)
355 naked_new_distroseries.hide_all_translations = False
347 transaction.commit()356 transaction.commit()
348 translatables = self._get_translatables()357 translatables = self._get_translatables()
349 self.failUnlessEqual(358 self.failUnlessEqual(
@@ -365,7 +374,7 @@
365 translatables,374 translatables,
366 self._ref_translatables(u"sampleseries")))375 self._ref_translatables(u"sampleseries")))
367376
368 new_distroseries.hide_all_translations = True377 naked_new_distroseries.hide_all_translations = True
369 transaction.commit()378 transaction.commit()
370 translatables = self._get_translatables()379 translatables = self._get_translatables()
371 self.failUnlessEqual(380 self.failUnlessEqual(
372381
=== modified file 'lib/lp/registry/tests/test_sourcepackage.py'
--- lib/lp/registry/tests/test_sourcepackage.py 2010-02-24 03:06:54 +0000
+++ lib/lp/registry/tests/test_sourcepackage.py 2010-07-23 12:49:25 +0000
@@ -22,6 +22,7 @@
22from lp.code.interfaces.seriessourcepackagebranch import (22from lp.code.interfaces.seriessourcepackagebranch import (
23 IMakeOfficialBranchLinks)23 IMakeOfficialBranchLinks)
24from lp.testing import TestCaseWithFactory24from lp.testing import TestCaseWithFactory
25from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
25from lp.testing.views import create_initialized_view26from lp.testing.views import create_initialized_view
26from canonical.testing.layers import DatabaseFunctionalLayer27from canonical.testing.layers import DatabaseFunctionalLayer
2728
@@ -252,11 +253,17 @@
252253
253 self.obsolete_productseries = self.factory.makeProductSeries(254 self.obsolete_productseries = self.factory.makeProductSeries(
254 name='obsolete', product=self.product)255 name='obsolete', product=self.product)
255 self.obsolete_productseries.status = SeriesStatus.OBSOLETE256 naked_obsolete_productseries = (
257 remove_security_proxy_and_shout_at_engineer(
258 self.obsolete_productseries))
259 naked_obsolete_productseries.status = SeriesStatus.OBSOLETE
256260
257 self.dev_productseries = self.factory.makeProductSeries(261 self.dev_productseries = self.factory.makeProductSeries(
258 name='current', product=self.product)262 name='current', product=self.product)
259 self.dev_productseries.status = SeriesStatus.DEVELOPMENT263 naked_dev_productseries = (
264 remove_security_proxy_and_shout_at_engineer(
265 self.dev_productseries))
266 naked_dev_productseries.status = SeriesStatus.DEVELOPMENT
260267
261 self.distribution = self.factory.makeDistribution(268 self.distribution = self.factory.makeDistribution(
262 name='youbuntu', displayname='Youbuntu', owner=self.owner)269 name='youbuntu', displayname='Youbuntu', owner=self.owner)
263270
=== modified file 'lib/lp/soyuz/adapters/tests/test_packagelocation.py'
--- lib/lp/soyuz/adapters/tests/test_packagelocation.py 2010-06-22 12:08:51 +0000
+++ lib/lp/soyuz/adapters/tests/test_packagelocation.py 2010-07-23 12:49:25 +0000
@@ -12,8 +12,10 @@
12from lp.soyuz.interfaces.archive import ArchivePurpose12from lp.soyuz.interfaces.archive import ArchivePurpose
13from lp.soyuz.interfaces.component import IComponentSet13from lp.soyuz.interfaces.component import IComponentSet
14from lp.testing import TestCaseWithFactory14from lp.testing import TestCaseWithFactory
15from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
15from canonical.testing import LaunchpadZopelessLayer16from canonical.testing import LaunchpadZopelessLayer
1617
18
17class TestPackageLocation(TestCaseWithFactory):19class TestPackageLocation(TestCaseWithFactory):
18 """Test the `PackageLocation` class."""20 """Test the `PackageLocation` class."""
19 layer = LaunchpadZopelessLayer21 layer = LaunchpadZopelessLayer
@@ -34,7 +36,8 @@
34 returned_location = self.factory.makeCopyArchiveLocation(36 returned_location = self.factory.makeCopyArchiveLocation(
35 distribution=ubuntu, name='now-comes-the-mystery',37 distribution=ubuntu, name='now-comes-the-mystery',
36 owner=self.factory.makePerson(name='mysteryman'))38 owner=self.factory.makePerson(name='mysteryman'))
37 copy_archive = returned_location.archive39 copy_archive = remove_security_proxy_and_shout_at_engineer(
40 returned_location).archive
3841
39 # Now use the created copy archive to test the build_package_location42 # Now use the created copy archive to test the build_package_location
40 # helper (called via getPackageLocation):43 # helper (called via getPackageLocation):
4144
=== modified file 'lib/lp/soyuz/browser/tests/archive-views.txt'
--- lib/lp/soyuz/browser/tests/archive-views.txt 2010-05-21 10:59:09 +0000
+++ lib/lp/soyuz/browser/tests/archive-views.txt 2010-07-23 12:49:25 +0000
@@ -15,11 +15,14 @@
1515
16 >>> from lp.registry.interfaces.distribution import (16 >>> from lp.registry.interfaces.distribution import (
17 ... IDistributionSet)17 ... IDistributionSet)
18 >>> from lp.testing.factory import (
19 ... remove_security_proxy_and_shout_at_engineer)
18 >>> ubuntu = getUtility(IDistributionSet)['ubuntu']20 >>> ubuntu = getUtility(IDistributionSet)['ubuntu']
19 >>> copy_location = factory.makeCopyArchiveLocation(21 >>> copy_location = factory.makeCopyArchiveLocation(
20 ... distribution=ubuntu,22 ... distribution=ubuntu,
21 ... name="intrepid-security-rebuild")23 ... name="intrepid-security-rebuild")
22 >>> copy_archive = copy_location.archive24 >>> copy_archive = remove_security_proxy_and_shout_at_engineer(
25 ... copy_location).archive
2326
24And let's create two views to compare:27And let's create two views to compare:
2528
@@ -65,8 +68,10 @@
65 068 0
6669
67 # Create a copy-request to Celso's PPA.70 # Create a copy-request to Celso's PPA.
71 >>> naked_copy_location = remove_security_proxy_and_shout_at_engineer(
72 ... copy_location)
68 >>> package_copy_request = ubuntu.main_archive.requestPackageCopy(73 >>> package_copy_request = ubuntu.main_archive.requestPackageCopy(
69 ... copy_location, copy_archive.owner)74 ... naked_copy_location, copy_archive.owner)
7075
71 >>> len(copy_archive_view.package_copy_requests)76 >>> len(copy_archive_view.package_copy_requests)
72 177 1
7378
=== modified file 'lib/lp/soyuz/browser/tests/test_distrosourcepackagerelease.py'
--- lib/lp/soyuz/browser/tests/test_distrosourcepackagerelease.py 2010-07-20 12:06:36 +0000
+++ lib/lp/soyuz/browser/tests/test_distrosourcepackagerelease.py 2010-07-23 12:49:25 +0000
@@ -14,6 +14,7 @@
14 DistributionSourcePackageRelease)14 DistributionSourcePackageRelease)
15from lp.soyuz.tests.test_publishing import SoyuzTestPublisher15from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
16from lp.testing import TestCaseWithFactory16from lp.testing import TestCaseWithFactory
17from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
17from lp.testing.views import create_initialized_view18from lp.testing.views import create_initialized_view
1819
1920
@@ -27,7 +28,12 @@
27 # The package must be published for the page to render.28 # The package must be published for the page to render.
28 stp = SoyuzTestPublisher()29 stp = SoyuzTestPublisher()
29 distroseries = stp.setUpDefaultDistroSeries()30 distroseries = stp.setUpDefaultDistroSeries()
30 distro = distroseries.distribution31 naked_distroseries = remove_security_proxy_and_shout_at_engineer(
32 distroseries)
33 # XXX Abel Deuring, 2010-07-21, bug 608240. This is scary. But
34 # if we use distroseries.distribution instead,
35 # test_spr_files_deleted() and test_spr_files_one() fail.
36 distro = naked_distroseries.distribution
31 source_package_release = stp.getPubSource().sourcepackagerelease37 source_package_release = stp.getPubSource().sourcepackagerelease
32 self.dspr = DistributionSourcePackageRelease(38 self.dspr = DistributionSourcePackageRelease(
33 distro, source_package_release)39 distro, source_package_release)
3440
=== modified file 'lib/lp/soyuz/browser/tests/test_sourcepackagerelease.py'
--- lib/lp/soyuz/browser/tests/test_sourcepackagerelease.py 2010-07-20 12:06:36 +0000
+++ lib/lp/soyuz/browser/tests/test_sourcepackagerelease.py 2010-07-23 12:49:25 +0000
@@ -16,6 +16,7 @@
16from canonical.testing import (16from canonical.testing import (
17 DatabaseFunctionalLayer, LaunchpadFunctionalLayer)17 DatabaseFunctionalLayer, LaunchpadFunctionalLayer)
18from lp.testing import TestCaseWithFactory18from lp.testing import TestCaseWithFactory
19from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
19from lp.testing.views import create_initialized_view20from lp.testing.views import create_initialized_view
2021
2122
@@ -64,20 +65,23 @@
6465
65 def test_highlighted_copyright_is_None(self):66 def test_highlighted_copyright_is_None(self):
66 expected = ''67 expected = ''
67 self.source_package_release.copyright = None68 remove_security_proxy_and_shout_at_engineer(
69 self.source_package_release).copyright = None
68 view = create_initialized_view(70 view = create_initialized_view(
69 self.source_package_release, '+copyright')71 self.source_package_release, '+copyright')
70 self.assertEqual(expected, view.highlighted_copyright)72 self.assertEqual(expected, view.highlighted_copyright)
7173
72 def test_highlighted_copyright_no_matches(self):74 def test_highlighted_copyright_no_matches(self):
73 expected = 'nothing to see and/or do.'75 expected = 'nothing to see and/or do.'
74 self.source_package_release.copyright = expected76 remove_security_proxy_and_shout_at_engineer(
77 self.source_package_release).copyright = expected
75 view = create_initialized_view(78 view = create_initialized_view(
76 self.source_package_release, '+copyright')79 self.source_package_release, '+copyright')
77 self.assertEqual(expected, view.highlighted_copyright)80 self.assertEqual(expected, view.highlighted_copyright)
7881
79 def test_highlighted_copyright_match_url(self):82 def test_highlighted_copyright_match_url(self):
80 self.source_package_release.copyright = (83 remove_security_proxy_and_shout_at_engineer(
84 self.source_package_release).copyright = (
81 'Downloaded from https://upstream.dom/fnord/no/ and')85 'Downloaded from https://upstream.dom/fnord/no/ and')
82 expected = (86 expected = (
83 'Downloaded from '87 'Downloaded from '
@@ -88,7 +92,8 @@
88 self.assertEqual(expected, view.highlighted_copyright)92 self.assertEqual(expected, view.highlighted_copyright)
8993
90 def test_highlighted_copyright_match_path(self):94 def test_highlighted_copyright_match_path(self):
91 self.source_package_release.copyright = (95 remove_security_proxy_and_shout_at_engineer(
96 self.source_package_release).copyright = (
92 'See /usr/share/common-licenses/GPL')97 'See /usr/share/common-licenses/GPL')
93 expected = (98 expected = (
94 'See '99 'See '
@@ -98,7 +103,8 @@
98 self.assertEqual(expected, view.highlighted_copyright)103 self.assertEqual(expected, view.highlighted_copyright)
99104
100 def test_highlighted_copyright_match_multiple(self):105 def test_highlighted_copyright_match_multiple(self):
101 self.source_package_release.copyright = (106 remove_security_proxy_and_shout_at_engineer(
107 self.source_package_release).copyright = (
102 'See /usr/share/common-licenses/GPL or https://osi.org/mit')108 'See /usr/share/common-licenses/GPL or https://osi.org/mit')
103 expected = (109 expected = (
104 'See '110 'See '
105111
=== modified file 'lib/lp/soyuz/doc/archive.txt'
--- lib/lp/soyuz/doc/archive.txt 2010-07-21 09:41:48 +0000
+++ lib/lp/soyuz/doc/archive.txt 2010-07-23 12:49:25 +0000
@@ -1285,10 +1285,15 @@
1285The IArchive interface includes a convenience method for creating a1285The IArchive interface includes a convenience method for creating a
1286package copy request:1286package copy request:
12871287
1288 >>> from lp.testing.factory import (
1289 ... remove_security_proxy_and_shout_at_engineer)
1288 >>> requestor = factory.makePerson(name='me-copy')1290 >>> requestor = factory.makePerson(name='me-copy')
1289 >>> copy_target = factory.makeCopyArchiveLocation(1291 >>> copy_target = factory.makeCopyArchiveLocation(
1290 ... distribution=ubuntu, name='my-copy-archive', owner=requestor)1292 ... distribution=ubuntu, name='my-copy-archive', owner=requestor)
1291 >>> pcr = ubuntu.main_archive.requestPackageCopy(copy_target, requestor)1293 >>> naked_copy_target = remove_security_proxy_and_shout_at_engineer(
1294 ... copy_target)
1295 >>> pcr = ubuntu.main_archive.requestPackageCopy(
1296 ... naked_copy_target, requestor)
1292 >>> print pcr1297 >>> print pcr
1293 Package copy request1298 Package copy request
1294 source = primary/hoary/-/RELEASE1299 source = primary/hoary/-/RELEASE
@@ -1301,7 +1306,7 @@
1301The requestPackageCopy method can also take an optional suite name:1306The requestPackageCopy method can also take an optional suite name:
13021307
1303 >>> package_copy_request = ubuntu.main_archive.requestPackageCopy(1308 >>> package_copy_request = ubuntu.main_archive.requestPackageCopy(
1304 ... copy_target, requestor, suite="hoary-updates");1309 ... naked_copy_target, requestor, suite="hoary-updates");
1305 >>> print package_copy_request1310 >>> print package_copy_request
1306 Package copy request1311 Package copy request
1307 source = primary/hoary/-/UPDATES1312 source = primary/hoary/-/UPDATES
@@ -1412,9 +1417,9 @@
14121417
1413COPY archives use a URL format of <distro-name>-<archive-name>:1418COPY archives use a URL format of <distro-name>-<archive-name>:
14141419
1415 >>> print copy_target.archive.is_copy1420 >>> print naked_copy_target.archive.is_copy
1416 True1421 True
1417 >>> print copy_target.archive.archive_url1422 >>> print naked_copy_target.archive.archive_url
1418 http://rebuild-test.internal/ubuntu-my-copy-archive/ubuntu1423 http://rebuild-test.internal/ubuntu-my-copy-archive/ubuntu
14191424
1420If the archive is private, the url may be different as private PPAs1425If the archive is private, the url may be different as private PPAs
14211426
=== modified file 'lib/lp/soyuz/stories/soyuz/xx-distribution-archives.txt'
--- lib/lp/soyuz/stories/soyuz/xx-distribution-archives.txt 2010-01-12 08:24:36 +0000
+++ lib/lp/soyuz/stories/soyuz/xx-distribution-archives.txt 2010-07-23 12:49:25 +0000
@@ -6,6 +6,8 @@
6 >>> from canonical.launchpad.interfaces import (6 >>> from canonical.launchpad.interfaces import (
7 ... IDistributionSet)7 ... IDistributionSet)
8 >>> from lp.registry.interfaces.person import IPersonSet8 >>> from lp.registry.interfaces.person import IPersonSet
9 >>> from lp.testing.factory import (
10 ... remove_security_proxy_and_shout_at_engineer)
9 >>> login('foo.bar@canonical.com')11 >>> login('foo.bar@canonical.com')
10 >>> ubuntu = getUtility(IDistributionSet)['ubuntu']12 >>> ubuntu = getUtility(IDistributionSet)['ubuntu']
1113
@@ -13,18 +15,21 @@
13 >>> copy_location = factory.makeCopyArchiveLocation(15 >>> copy_location = factory.makeCopyArchiveLocation(
14 ... distribution=ubuntu, owner=joe,16 ... distribution=ubuntu, owner=joe,
15 ... name="intrepid-security-rebuild")17 ... name="intrepid-security-rebuild")
16 >>> copy_archive = copy_location.archive18 >>> naked_copy_location = remove_security_proxy_and_shout_at_engineer(
19 ... copy_location)
20 >>> copy_archive = naked_copy_location.archive
17 >>> copy_archive.enabled21 >>> copy_archive.enabled
18 True22 True
1923
20 >>> package_copy_request = ubuntu.main_archive.requestPackageCopy(24 >>> package_copy_request = ubuntu.main_archive.requestPackageCopy(
21 ... copy_location, copy_archive.owner)25 ... naked_copy_location, copy_archive.owner)
2226
23 >>> nopriv = getUtility(IPersonSet).getByName('no-priv')27 >>> nopriv = getUtility(IPersonSet).getByName('no-priv')
24 >>> disabled_location = factory.makeCopyArchiveLocation(28 >>> disabled_location = factory.makeCopyArchiveLocation(
25 ... distribution=ubuntu, owner=nopriv,29 ... distribution=ubuntu, owner=nopriv,
26 ... name="disabled-security-rebuild", enabled=False)30 ... name="disabled-security-rebuild", enabled=False)
27 >>> disabled_archive = disabled_location.archive31 >>> disabled_archive = remove_security_proxy_and_shout_at_engineer(
32 ... disabled_location).archive
28 >>> disabled_archive.enabled33 >>> disabled_archive.enabled
29 False34 False
3035
@@ -119,12 +124,14 @@
119 >>> copy_location = factory.makeCopyArchiveLocation(124 >>> copy_location = factory.makeCopyArchiveLocation(
120 ... distribution=ubuntu,125 ... distribution=ubuntu,
121 ... name="intrepid-private-security-rebuild")126 ... name="intrepid-private-security-rebuild")
122 >>> copy_archive = copy_location.archive127 >>> naked_copy_location = remove_security_proxy_and_shout_at_engineer(
128 ... copy_location)
129 >>> copy_archive = naked_copy_location.archive
123 >>> copy_archive.buildd_secret = 'really secret'130 >>> copy_archive.buildd_secret = 'really secret'
124 >>> copy_archive.private = True131 >>> copy_archive.private = True
125 >>> copy_archive.owner.displayname = "Harry Potter"132 >>> copy_archive.owner.displayname = "Harry Potter"
126 >>> package_copy_request = ubuntu.main_archive.requestPackageCopy(133 >>> package_copy_request = ubuntu.main_archive.requestPackageCopy(
127 ... copy_location, copy_archive.owner)134 ... naked_copy_location, copy_archive.owner)
128 >>> pub_src = stp.getPubSource(135 >>> pub_src = stp.getPubSource(
129 ... archive=copy_archive, architecturehintlist='any')136 ... archive=copy_archive, architecturehintlist='any')
130 >>> pub_bins = stp.getPubBinaries(pub_source=pub_src)137 >>> pub_bins = stp.getPubBinaries(pub_source=pub_src)
131138
=== modified file 'lib/lp/soyuz/tests/test_publishing.py'
--- lib/lp/soyuz/tests/test_publishing.py 2010-07-23 12:49:10 +0000
+++ lib/lp/soyuz/tests/test_publishing.py 2010-07-23 12:49:25 +0000
@@ -43,7 +43,8 @@
43from lp.soyuz.interfaces.queue import PackageUploadStatus43from lp.soyuz.interfaces.queue import PackageUploadStatus
44from canonical.launchpad.scripts import FakeLogger44from canonical.launchpad.scripts import FakeLogger
45from lp.testing import TestCaseWithFactory45from lp.testing import TestCaseWithFactory
46from lp.testing.factory import LaunchpadObjectFactory46from lp.testing.factory import (
47 LaunchpadObjectFactory, remove_security_proxy_and_shout_at_engineer)
47from lp.testing.fakemethod import FakeMethod48from lp.testing.fakemethod import FakeMethod
4849
4950
@@ -145,7 +146,10 @@
145 PackageUploadStatus.DONE: 'setDone',146 PackageUploadStatus.DONE: 'setDone',
146 PackageUploadStatus.ACCEPTED: 'setAccepted',147 PackageUploadStatus.ACCEPTED: 'setAccepted',
147 }148 }
148 method = getattr(package_upload, status_to_method[upload_status])149 naked_package_upload = remove_security_proxy_and_shout_at_engineer(
150 package_upload)
151 method = getattr(
152 naked_package_upload, status_to_method[upload_status])
149 method()153 method()
150154
151 return package_upload155 return package_upload
@@ -221,7 +225,9 @@
221 changes_file_name=changes_file_name,225 changes_file_name=changes_file_name,
222 changes_file_content=changes_file_content,226 changes_file_content=changes_file_content,
223 upload_status=upload_status)227 upload_status=upload_status)
224 package_upload.addSource(spr)228 naked_package_upload = remove_security_proxy_and_shout_at_engineer(
229 package_upload)
230 naked_package_upload.addSource(spr)
225231
226 if filename is None:232 if filename is None:
227 filename = "%s_%s.dsc" % (sourcename, version)233 filename = "%s_%s.dsc" % (sourcename, version)
228234
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2010-07-23 12:49:10 +0000
+++ lib/lp/testing/factory.py 2010-07-23 12:49:25 +0000
@@ -13,8 +13,10 @@
13__metaclass__ = type13__metaclass__ = type
14__all__ = [14__all__ = [
15 'GPGSigningContext',15 'GPGSigningContext',
16 'is_security_proxied_or_harmless',
16 'LaunchpadObjectFactory',17 'LaunchpadObjectFactory',
17 'ObjectFactory',18 'ObjectFactory',
19 'remove_security_proxy_and_shout_at_engineer',
18 ]20 ]
1921
20from contextlib import nested22from contextlib import nested
@@ -25,18 +27,22 @@
25from email.mime.text import MIMEText27from email.mime.text import MIMEText
26from email.mime.multipart import MIMEMultipart28from email.mime.multipart import MIMEMultipart
27from itertools import count29from itertools import count
30from operator import isSequenceType
28import os.path31import os.path
29from random import randint32from random import randint
30from StringIO import StringIO33from StringIO import StringIO
34import sys
31from textwrap import dedent35from textwrap import dedent
32from threading import local36from threading import local
37from types import InstanceType
3338
34import pytz39import pytz
3540
36from twisted.python.util import mergeFunctionMetadata41from twisted.python.util import mergeFunctionMetadata
3742
38from zope.component import ComponentLookupError, getUtility43from zope.component import ComponentLookupError, getUtility
39from zope.security.proxy import removeSecurityProxy44from zope.security.proxy import (
45 builtin_isinstance, Proxy, ProxyFactory, removeSecurityProxy)
4046
41from canonical.autodecorate import AutoDecorate47from canonical.autodecorate import AutoDecorate
42from canonical.config import config48from canonical.config import config
@@ -152,7 +158,6 @@
152 ANONYMOUS,158 ANONYMOUS,
153 login,159 login,
154 login_as,160 login_as,
155 logout,
156 run_with_login,161 run_with_login,
157 temp_dir,162 temp_dir,
158 time_counter,163 time_counter,
@@ -325,7 +330,7 @@
325 branch_id, rcstype, url, cvs_root, cvs_module)330 branch_id, rcstype, url, cvs_root, cvs_module)
326331
327332
328class LaunchpadObjectFactory(ObjectFactory):333class BareLaunchpadObjectFactory(ObjectFactory):
329 """Factory methods for creating Launchpad objects.334 """Factory methods for creating Launchpad objects.
330335
331 All the factory methods should be callable with no parameters.336 All the factory methods should be callable with no parameters.
@@ -356,7 +361,7 @@
356361
357 location = PackageLocation(copy_archive, distribution, distroseries,362 location = PackageLocation(copy_archive, distribution, distroseries,
358 pocket)363 pocket)
359 return location364 return ProxyFactory(location)
360365
361 def makeAccount(self, displayname, email=None, password=None,366 def makeAccount(self, displayname, email=None, password=None,
362 status=AccountStatus.ACTIVE,367 status=AccountStatus.ACTIVE,
@@ -744,8 +749,8 @@
744 # We don't want to login() as the person used to create the product,749 # We don't want to login() as the person used to create the product,
745 # so we remove the security proxy before creating the series.750 # so we remove the security proxy before creating the series.
746 naked_product = removeSecurityProxy(product)751 naked_product = removeSecurityProxy(product)
747 return naked_product.newSeries(owner=owner, name=name,752 return ProxyFactory(
748 summary=summary)753 naked_product.newSeries(owner=owner, name=name, summary=summary))
749754
750 def makeProject(self, name=None, displayname=None, title=None,755 def makeProject(self, name=None, displayname=None, title=None,
751 homepageurl=None, summary=None, owner=None,756 homepageurl=None, summary=None, owner=None,
@@ -828,7 +833,7 @@
828 url = self.getUniqueURL()833 url = self.getUniqueURL()
829 else:834 else:
830 raise UnknownBranchTypeError(835 raise UnknownBranchTypeError(
831 'Unrecognized branch type: %r' % (branch_type,))836 'Unrecognized branch type: %r' % (branch_type, ))
832837
833 namespace = get_branch_namespace(838 namespace = get_branch_namespace(
834 owner, product=product, distroseries=distroseries,839 owner, product=product, distroseries=distroseries,
@@ -1034,7 +1039,7 @@
1034 CodeReviewNotificationLevel.NOEMAIL, subscribed_by)1039 CodeReviewNotificationLevel.NOEMAIL, subscribed_by)
10351040
1036 def makeDiff(self, diff_text=DIFF):1041 def makeDiff(self, diff_text=DIFF):
1037 return Diff.fromFile(StringIO(diff_text), len(diff_text))1042 return ProxyFactory(Diff.fromFile(StringIO(diff_text), len(diff_text)))
10381043
1039 def makePreviewDiff(self, conflicts=u''):1044 def makePreviewDiff(self, conflicts=u''):
1040 diff = self.makeDiff()1045 diff = self.makeDiff()
@@ -1614,6 +1619,7 @@
1614 return series1619 return series
16151620
1616 def makeLanguage(self, language_code=None, name=None):1621 def makeLanguage(self, language_code=None, name=None):
1622 """Makes a language given the language_code and name."""
1617 if language_code is None:1623 if language_code is None:
1618 language_code = self.getUniqueString('lang')1624 language_code = self.getUniqueString('lang')
1619 if name is None:1625 if name is None:
@@ -1680,7 +1686,7 @@
1680 description=self.getUniqueString(),1686 description=self.getUniqueString(),
1681 parent_series=parent_series, owner=distribution.owner)1687 parent_series=parent_series, owner=distribution.owner)
1682 series.status = status1688 series.status = status
1683 return series1689 return ProxyFactory(series)
16841690
1685 # Most people think of distro releases as distro series.1691 # Most people think of distro releases as distro series.
1686 makeDistroSeries = makeDistroRelease1692 makeDistroSeries = makeDistroRelease
@@ -1789,7 +1795,7 @@
17891795
1790 def makeRecipeText(self, *branches):1796 def makeRecipeText(self, *branches):
1791 if len(branches) == 0:1797 if len(branches) == 0:
1792 branches = (self.makeAnyBranch(),)1798 branches = (self.makeAnyBranch(), )
1793 base_branch = branches[0]1799 base_branch = branches[0]
1794 other_branches = branches[1:]1800 other_branches = branches[1:]
1795 text = MINIMAL_RECIPE_TEXT % base_branch.bzr_identity1801 text = MINIMAL_RECIPE_TEXT % base_branch.bzr_identity
@@ -1952,7 +1958,7 @@
1952 productseries = self.makeProductSeries(owner=owner)1958 productseries = self.makeProductSeries(owner=owner)
1953 # Make it use Translations, otherwise there's little point1959 # Make it use Translations, otherwise there's little point
1954 # to us creating a template for it.1960 # to us creating a template for it.
1955 productseries.product.official_rosetta = True1961 removeSecurityProxy(productseries).product.official_rosetta = True
1956 templateset = getUtility(IPOTemplateSet)1962 templateset = getUtility(IPOTemplateSet)
1957 subset = templateset.getSubset(1963 subset = templateset.getSubset(
1958 distroseries, sourcepackagename, productseries)1964 distroseries, sourcepackagename, productseries)
@@ -2727,3 +2733,73 @@
2727 new_uuid = getUtility(ITemporaryStorageManager).new(blob, expires)2733 new_uuid = getUtility(ITemporaryStorageManager).new(blob, expires)
27282734
2729 return getUtility(ITemporaryStorageManager).fetch(new_uuid)2735 return getUtility(ITemporaryStorageManager).fetch(new_uuid)
2736
2737
2738# Some factory methods return simple Python types. We don't add
2739# security wrappers for them, as well as for objects created by
2740# other Python libraries.
2741unwrapped_types = (
2742 DSCFile, InstanceType, Message, datetime, int, str, unicode)
2743
2744def is_security_proxied_or_harmless(obj):
2745 """Check that the given object is security wrapped or a harmless object."""
2746 if obj is None:
2747 return True
2748 if builtin_isinstance(obj, Proxy):
2749 return True
2750 if type(obj) in unwrapped_types:
2751 return True
2752 if isSequenceType(obj):
2753 for element in obj:
2754 if not is_security_proxied_or_harmless(element):
2755 return False
2756 return True
2757 return False
2758
2759
2760class LaunchpadObjectFactory:
2761 """A wrapper around `BareLaunchpadObjectFactory`.
2762
2763 Ensure that each object created by a `BareLaunchpadObjectFactory` method
2764 is either of a simple Python type or is security proxied.
2765
2766 A warning message is printed to stderr if a factory method creates
2767 an object without a security proxy.
2768
2769 Whereever you see such a warning: fix it!
2770 """
2771 def __init__(self):
2772 self._factory = BareLaunchpadObjectFactory()
2773
2774 def __getattr__(self, name):
2775 attr = getattr(self._factory, name)
2776 if callable(attr):
2777
2778 def guarded_method(*args, **kw):
2779 result = attr(*args, **kw)
2780 if not is_security_proxied_or_harmless(result):
2781 message = (
2782 "PLEASE FIX: LaunchpadObjectFactory.%s returns an "
2783 "unproxied object." % name)
2784 print >>sys.stderr, message
2785 return result
2786 return guarded_method
2787 else:
2788 return attr
2789
2790
2791def remove_security_proxy_and_shout_at_engineer(obj):
2792 """Remove an object's security proxy and print a warning.
2793
2794 A number of LaunchpadObjectFactory methods returned objects without
2795 a security proxy. This is now no longer possible, but a number of
2796 tests rely on unrestricted access to object attributes.
2797
2798 This function should only be used in legacy tests which fail because
2799 they expect unproxied objects.
2800 """
2801 print >>sys.stderr, (
2802 "\nWarning: called removeSecurityProxy() for %r without a check if "
2803 "this reasonable. Look for a call of "
2804 "remove_security_proxy_and_shout_at_engineer(some_object)." % obj)
2805 return removeSecurityProxy(obj)
27302806
=== modified file 'lib/lp/testing/tests/test_factory.py'
--- lib/lp/testing/tests/test_factory.py 2010-07-23 12:49:10 +0000
+++ lib/lp/testing/tests/test_factory.py 2010-07-23 12:49:25 +0000
@@ -8,12 +8,14 @@
8import unittest8import unittest
99
10from zope.component import getUtility10from zope.component import getUtility
11from zope.security.proxy import removeSecurityProxy
1112
12from canonical.launchpad.webapp.interfaces import ILaunchBag13from canonical.launchpad.webapp.interfaces import ILaunchBag
13from canonical.testing.layers import DatabaseFunctionalLayer14from canonical.testing.layers import DatabaseFunctionalLayer
14from lp.code.enums import CodeImportReviewStatus15from lp.code.enums import CodeImportReviewStatus
15from lp.testing import TestCaseWithFactory16from lp.testing import TestCaseWithFactory
16from lp.services.worlddata.interfaces.language import ILanguage17from lp.services.worlddata.interfaces.language import ILanguage
18from lp.testing.factory import is_security_proxied_or_harmless
1719
1820
19class TestFactory(TestCaseWithFactory):21class TestFactory(TestCaseWithFactory):
@@ -66,6 +68,54 @@
66 self.assertIsNot(None, person)68 self.assertIsNot(None, person)
67 self.assertEqual(person, current_person)69 self.assertEqual(person, current_person)
6870
71 def test_is_security_proxied_or_harmless__none(self):
72 # is_security_proxied_or_harmless() considers the None object
73 # to be a harmless object.
74 self.assertTrue(is_security_proxied_or_harmless(None))
75
76 def test_is_security_proxied_or_harmless__int(self):
77 # is_security_proxied_or_harmless() considers integers
78 # to be harmless.
79 self.assertTrue(is_security_proxied_or_harmless(1))
80
81 def test_is_security_proxied_or_harmless__string(self):
82 # is_security_proxied_or_harmless() considers strings
83 # to be harmless.
84 self.assertTrue(is_security_proxied_or_harmless('abc'))
85
86 def test_is_security_proxied_or_harmless__unicode(self):
87 # is_security_proxied_or_harmless() considers unicode objects
88 # to be harmless.
89 self.assertTrue(is_security_proxied_or_harmless(u'abc'))
90
91 def test_is_security_proxied_or_harmless__proxied_object(self):
92 # is_security_proxied_or_harmless() treats security proxied
93 # objects as harmless.
94 proxied_person = self.factory.makePerson()
95 self.assertTrue(is_security_proxied_or_harmless(proxied_person))
96
97 def test_is_security_proxied_or_harmless__unproxied_object(self):
98 # is_security_proxied_or_harmless() treats security proxied
99 # objects as harmless.
100 unproxied_person = removeSecurityProxy(self.factory.makePerson())
101 self.assertFalse(is_security_proxied_or_harmless(unproxied_person))
102
103 def test_is_security_proxied_or_harmless__sequence_harmless_content(self):
104 # is_security_proxied_or_harmless() checks all elements
105 # of a sequence. If all elements are harmless, so is the
106 # sequence.
107 proxied_person = self.factory.makePerson()
108 self.assertTrue(
109 is_security_proxied_or_harmless([1, '2', proxied_person]))
110
111 def test_is_security_proxied_or_harmless__sequence_harmful_content(self):
112 # is_security_proxied_or_harmless() checks all elements
113 # of a sequence. If at least one element is harmful, so is the
114 # sequence.
115 unproxied_person = removeSecurityProxy(self.factory.makePerson())
116 self.assertFalse(
117 is_security_proxied_or_harmless([1, '2', unproxied_person]))
118
69119
70def test_suite():120def test_suite():
71 return unittest.TestLoader().loadTestsFromName(__name__)121 return unittest.TestLoader().loadTestsFromName(__name__)
72122
=== modified file 'lib/lp/translations/browser/configure.zcml'
--- lib/lp/translations/browser/configure.zcml 2010-07-23 12:49:10 +0000
+++ lib/lp/translations/browser/configure.zcml 2010-07-23 12:49:25 +0000
@@ -269,7 +269,7 @@
269 <browser:url269 <browser:url
270 for="lp.translations.interfaces.productserieslanguage.IProductSeriesLanguage"270 for="lp.translations.interfaces.productserieslanguage.IProductSeriesLanguage"
271 path_expression="string:+lang/${language/code}"271 path_expression="string:+lang/${language/code}"
272 attribute_to_parent="productseries"272 attribute_to_parent="parent"
273 rootsite="translations"/>273 rootsite="translations"/>
274 <browser:navigation274 <browser:navigation
275 module="lp.translations.browser.serieslanguage"275 module="lp.translations.browser.serieslanguage"
276276
=== modified file 'lib/lp/translations/browser/serieslanguage.py'
--- lib/lp/translations/browser/serieslanguage.py 2010-03-04 07:31:38 +0000
+++ lib/lp/translations/browser/serieslanguage.py 2010-07-23 12:49:25 +0000
@@ -29,7 +29,7 @@
2929
3030
31class BaseSeriesLanguageView(LaunchpadView):31class BaseSeriesLanguageView(LaunchpadView):
32 """View base class to render translation status for an 32 """View base class to render translation status for an
33 `IDistroSeries` and `IProductSeries`33 `IDistroSeries` and `IProductSeries`
3434
35 This class should not be directly instantiated.35 This class should not be directly instantiated.
@@ -46,12 +46,15 @@
46 self.translationgroup = translationgroup46 self.translationgroup = translationgroup
47 self.form = self.request.form47 self.form = self.request.form
4848
49 self.batchnav = BatchNavigator(49 if IDistroSeriesLanguage.providedBy(self.context):
50 self.series.getCurrentTranslationTemplates(),50 self.batchnav = BatchNavigator(
51 self.request)51 self.series.getCurrentTranslationTemplates(),
5252 self.request)
53 self.pofiles = self.context.getPOFilesFor(53 self.pofiles = self.context.getPOFilesFor(
54 self.batchnav.currentBatch())54 self.batchnav.currentBatch())
55 else:
56 self.batchnav = BatchNavigator(self.context.pofiles, self.request)
57 self.pofiles = self.batchnav.currentBatch()
5558
56 @property59 @property
57 def translation_group(self):60 def translation_group(self):
@@ -77,7 +80,7 @@
77 @property80 @property
78 def access_level_description(self):81 def access_level_description(self):
79 """Must not be called when there's no translation group."""82 """Must not be called when there's no translation group."""
80 83
81 if is_read_only():84 if is_read_only():
82 return (85 return (
83 "No work can be done on these translations while Launchpad "86 "No work can be done on these translations while Launchpad "
8487
=== modified file 'lib/lp/translations/browser/tests/test_breadcrumbs.py'
--- lib/lp/translations/browser/tests/test_breadcrumbs.py 2010-07-19 15:31:57 +0000
+++ lib/lp/translations/browser/tests/test_breadcrumbs.py 2010-07-23 12:49:25 +0000
@@ -9,6 +9,7 @@
99
10from lp.services.worlddata.interfaces.language import ILanguageSet10from lp.services.worlddata.interfaces.language import ILanguageSet
11from lp.testing.breadcrumbs import BaseBreadcrumbTestCase11from lp.testing.breadcrumbs import BaseBreadcrumbTestCase
12from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
12from lp.translations.interfaces.distroserieslanguage import (13from lp.translations.interfaces.distroserieslanguage import (
13 IDistroSeriesLanguageSet)14 IDistroSeriesLanguageSet)
14from lp.translations.interfaces.productserieslanguage import (15from lp.translations.interfaces.productserieslanguage import (
@@ -109,7 +110,8 @@
109 name='crumb-tester', displayname="Crumb Tester")110 name='crumb-tester', displayname="Crumb Tester")
110 series = self.factory.makeDistroRelease(111 series = self.factory.makeDistroRelease(
111 name="test", version="1.0", distribution=distribution)112 name="test", version="1.0", distribution=distribution)
112 series.hide_all_translations = False113 naked_series = remove_security_proxy_and_shout_at_engineer(series)
114 naked_series.hide_all_translations = False
113 serieslanguage = getUtility(IDistroSeriesLanguageSet).getDummy(115 serieslanguage = getUtility(IDistroSeriesLanguageSet).getDummy(
114 series, self.language)116 series, self.language)
115117
116118
=== modified file 'lib/lp/translations/configure.zcml'
--- lib/lp/translations/configure.zcml 2010-07-23 12:49:10 +0000
+++ lib/lp/translations/configure.zcml 2010-07-23 12:49:25 +0000
@@ -399,6 +399,16 @@
399 interface="lp.translations.interfaces.productserieslanguage.IProductSeriesLanguageSet"/>399 interface="lp.translations.interfaces.productserieslanguage.IProductSeriesLanguageSet"/>
400 </securedutility>400 </securedutility>
401401
402 <!-- TranslatedLanguage -->
403 <facet
404 facet="translations">
405 <class
406 class="lp.translations.model.translatedlanguage.POFilesByPOTemplates">
407 <allow
408 interface="lp.translations.interfaces.translatedlanguage.IPOFilesByPOTemplates"/>
409 </class>
410 </facet>
411
402 <!-- POTemplate -->412 <!-- POTemplate -->
403 <facet413 <facet
404 facet="translations">414 facet="translations">
@@ -430,6 +440,13 @@
430 provides="lp.translations.interfaces.translationcommonformat.ITranslationFileData"440 provides="lp.translations.interfaces.translationcommonformat.ITranslationFileData"
431 factory="lp.translations.model.potemplate.POTemplateToTranslationFileDataAdapter"/>441 factory="lp.translations.model.potemplate.POTemplateToTranslationFileDataAdapter"/>
432442
443 <!-- TranslationTemplatesCollection -->
444 <class
445 class="lp.translations.model.potemplate.TranslationTemplatesCollection">
446 <allow
447 interface="lp.translations.interfaces.potemplate.ITranslationTemplatesCollection"/>
448 </class>
449
433 <!-- POTemplateSet -->450 <!-- POTemplateSet -->
434451
435 <securedutility452 <securedutility
436453
=== modified file 'lib/lp/translations/doc/translations-export-to-branch.txt'
--- lib/lp/translations/doc/translations-export-to-branch.txt 2010-06-16 07:22:57 +0000
+++ lib/lp/translations/doc/translations-export-to-branch.txt 2010-07-23 12:49:25 +0000
@@ -220,8 +220,12 @@
220 >>> from email import message_from_string220 >>> from email import message_from_string
221 >>> from lp.services.mail import stub221 >>> from lp.services.mail import stub
222 >>> from lp.codehosting.vfs import get_rw_server222 >>> from lp.codehosting.vfs import get_rw_server
223 >>> from lp.testing.factory import (
224 ... remove_security_proxy_and_shout_at_engineer)
223 >>> productseries = factory.makeProductSeries()225 >>> productseries = factory.makeProductSeries()
224 >>> productseries.translations_branch = factory.makeBranch()226 >>> naked_productseries = remove_security_proxy_and_shout_at_engineer(
227 ... productseries)
228 >>> naked_productseries.translations_branch = factory.makeBranch()
225 >>> template = factory.makePOTemplate(productseries=productseries)229 >>> template = factory.makePOTemplate(productseries=productseries)
226 >>> potmsgset = factory.makePOTMsgSet(template)230 >>> potmsgset = factory.makePOTMsgSet(template)
227 >>> pofile = removeSecurityProxy(231 >>> pofile = removeSecurityProxy(
228232
=== modified file 'lib/lp/translations/interfaces/potemplate.py'
--- lib/lp/translations/interfaces/potemplate.py 2010-07-23 12:49:10 +0000
+++ lib/lp/translations/interfaces/potemplate.py 2010-07-23 12:49:25 +0000
@@ -781,5 +781,19 @@
781 exist for it.781 exist for it.
782 """782 """
783783
784class ITranslationTemplatesCollection(Interface):
785 """A `Collection` of `POTemplate`s."""
786
787 def joinOuterPOFile(language=None):
788 """Outer-join `POFile` into the collection.
789
790 :return: A `TranslationTemplatesCollection` with an added outer
791 join to `POFile`.
792 """
793
794 def select(*args):
795 """Return a ResultSet for this collection with values set to args."""
796
797
784# Monkey patch for circular import avoidance done in798# Monkey patch for circular import avoidance done in
785# _schema_circular_imports.py799# _schema_circular_imports.py
786800
=== modified file 'lib/lp/translations/interfaces/productserieslanguage.py'
--- lib/lp/translations/interfaces/productserieslanguage.py 2010-07-19 15:31:57 +0000
+++ lib/lp/translations/interfaces/productserieslanguage.py 2010-07-23 12:49:25 +0000
@@ -5,13 +5,13 @@
55
6from lazr.restful.fields import Reference6from lazr.restful.fields import Reference
77
8from zope.interface import Attribute, Interface8from zope.interface import Interface
9from zope.schema import (9from zope.schema import Choice, TextLine
10 Choice, Datetime, TextLine)
1110
12from canonical.launchpad import _11from canonical.launchpad import _
13from lp.translations.interfaces.pofile import IPOFile12from lp.translations.interfaces.pofile import IPOFile
14from lp.translations.interfaces.rosettastats import IRosettaStats13from lp.translations.interfaces.rosettastats import IRosettaStats
14from lp.translations.interfaces.translatedlanguage import ITranslatedLanguage
1515
16__metaclass__ = type16__metaclass__ = type
1717
@@ -21,13 +21,9 @@
21 ]21 ]
2222
2323
24class IProductSeriesLanguage(IRosettaStats):24class IProductSeriesLanguage(IRosettaStats, ITranslatedLanguage):
25 """Per-language statistics for a product series."""25 """Per-language statistics for a product series."""
2626
27 language = Choice(
28 title=_('Language to gather statistics for.'),
29 vocabulary='Language', required=True, readonly=True)
30
31 pofile = Reference(27 pofile = Reference(
32 title=_("A POFile if there is only one POTemplate for the series."),28 title=_("A POFile if there is only one POTemplate for the series."),
33 schema=IPOFile, required=False, readonly=True)29 schema=IPOFile, required=False, readonly=True)
@@ -41,27 +37,6 @@
41 title=_("Title for the per-language per-series page."),37 title=_("Title for the per-language per-series page."),
42 required=False)38 required=False)
4339
44 pofiles = Attribute("The set of pofiles in this distroseries for this "
45 "language. This includes only the real pofiles where translations "
46 "exist.")
47
48
49 last_changed_date = Datetime(
50 title=_('When this file was last changed.'))
51
52 def getPOFilesFor(potemplates):
53 """Return `POFiles` for each of `potemplates`, in the same order.
54
55 For any `POTemplate` that does not have a translation to the
56 required language, a `DummyPOFile` is provided.
57 """
58
59 def setCounts(total, imported, changed, new, unreviewed, last_changed):
60 """Set aggregated message counts for ProductSeriesLanguage."""
61
62 def recalculateCounts(total, imported, changed, new, unreviewed):
63 """Recalculate message counts for this ProductSeriesLanguage."""
64
6540
66class IProductSeriesLanguageSet(Interface):41class IProductSeriesLanguageSet(Interface):
67 """The set of productserieslanguages."""42 """The set of productserieslanguages."""
6843
=== added file 'lib/lp/translations/interfaces/translatedlanguage.py'
--- lib/lp/translations/interfaces/translatedlanguage.py 1970-01-01 00:00:00 +0000
+++ lib/lp/translations/interfaces/translatedlanguage.py 2010-07-23 12:49:25 +0000
@@ -0,0 +1,79 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4# pylint: disable-msg=E0211,E0213
5
6from zope.interface import Attribute, Interface
7from zope.interface.common.sequence import IFiniteSequence
8from zope.schema import Datetime, Object
9
10from canonical.launchpad import _
11from lp.services.worlddata.interfaces.language import ILanguage
12from lp.translations.interfaces.potemplate import IHasTranslationTemplates
13from lp.registry.interfaces.person import IPerson
14
15__metaclass__ = type
16
17__all__ = [
18 'IPOFilesByPOTemplates',
19 'ITranslatedLanguage',
20 ]
21
22
23class ITranslatedLanguage(Interface):
24 """Interface for providing translations for context by language.
25
26 It expects `parent` to provide `IHasTranslationTemplates`.
27 """
28
29 language = Object(
30 title=_('Language to gather statistics and POFiles for.'),
31 schema=ILanguage)
32
33 parent = Object(
34 title=_('A parent with translation templates.'),
35 schema=IHasTranslationTemplates)
36
37 pofiles = Attribute(
38 _('Iterator over all POFiles for this context and language.'))
39
40 translation_statistics = Attribute(
41 _('A dict containing relevant aggregated statistics counts.'))
42
43 def setCounts(total, translated, new, changed, unreviewed):
44 """Set aggregated message counts for ITranslatedLanguage."""
45
46 def recalculateCounts():
47 """Recalculate message counts for this ITranslatedLanguage."""
48
49 last_changed_date = Datetime(
50 title=_('When was this translation last changed.'),
51 readonly=False, required=True)
52
53 last_translator = Object(
54 title=_('Last person that translated something in this context.'),
55 schema=IPerson)
56
57
58class IPOFilesByPOTemplates(IFiniteSequence):
59 """Iterate `IPOFile`s for (`ILanguage`, `ITranslationTemplateCollection`).
60
61 This is a wrapper for Storm ResultSet that enables optimized slicing
62 by doing it lazily on the query, thus allowing DummyPOFile objects
63 to be returned while still not doing more than one database query.
64
65 It subclasses `IFiniteSequence` so it can easily be used with the
66 BatchNavigator.
67 """
68
69 def __getitem__(selector):
70 """Get an element or slice of `IPOFile`s for given templates."""
71
72 def __getslice__(start, end):
73 """Deprecated, and implemented through __getitem__."""
74
75 def __iter__():
76 """Iterates over all `IPOFile`s for given templates."""
77
78 def __len__():
79 """Provides count of `IPOTemplate`s in a template collection."""
080
=== modified file 'lib/lp/translations/model/potemplate.py'
--- lib/lp/translations/model/potemplate.py 2010-07-23 12:49:10 +0000
+++ lib/lp/translations/model/potemplate.py 2010-07-23 12:49:25 +0000
@@ -1565,7 +1565,8 @@
1565 @property1565 @property
1566 def has_current_translation_templates(self):1566 def has_current_translation_templates(self):
1567 """See `IHasTranslationTemplates`."""1567 """See `IHasTranslationTemplates`."""
1568 return bool(self.getCurrentTranslationTemplates(just_ids=True).any())1568 return bool(
1569 self.getCurrentTranslationTemplates(just_ids=True).any())
15691570
1570 def getCurrentTranslationFiles(self, just_ids=False):1571 def getCurrentTranslationFiles(self, just_ids=False):
1571 """See `IHasTranslationTemplates`."""1572 """See `IHasTranslationTemplates`."""
@@ -1660,10 +1661,16 @@
1660 """1661 """
1661 return self.joinInner(POFile, POTemplate.id == POFile.potemplateID)1662 return self.joinInner(POFile, POTemplate.id == POFile.potemplateID)
16621663
1663 def joinOuterPOFile(self):1664 def joinOuterPOFile(self, language=None):
1664 """Outer-join `POFile` into the collection.1665 """Outer-join `POFile` into the collection.
16651666
1666 :return: A `TranslationTemplatesCollection` with an added outer1667 :return: A `TranslationTemplatesCollection` with an added outer
1667 join to `POFile`.1668 join to `POFile`.
1668 """1669 """
1669 return self.joinOuter(POFile, POTemplate.id == POFile.potemplateID)1670 if language is not None:
1671 return self.joinOuter(
1672 POFile, And(POTemplate.id == POFile.potemplateID,
1673 POFile.languageID == language.id))
1674 else:
1675 return self.joinOuter(
1676 POFile, POTemplate.id == POFile.potemplateID)
16701677
=== modified file 'lib/lp/translations/model/productserieslanguage.py'
--- lib/lp/translations/model/productserieslanguage.py 2010-07-19 15:38:51 +0000
+++ lib/lp/translations/model/productserieslanguage.py 2010-07-23 12:49:25 +0000
@@ -12,17 +12,13 @@
1212
13from zope.interface import implements13from zope.interface import implements
1414
15from storm.expr import Coalesce, Sum
16from storm.store import Store
17
18from lp.translations.utilities.rosettastats import RosettaStats15from lp.translations.utilities.rosettastats import RosettaStats
19from lp.translations.model.pofile import POFile16from lp.translations.model.translatedlanguage import TranslatedLanguageMixin
20from lp.translations.model.potemplate import get_pofiles_for, POTemplate
21from lp.translations.interfaces.productserieslanguage import (17from lp.translations.interfaces.productserieslanguage import (
22 IProductSeriesLanguage, IProductSeriesLanguageSet)18 IProductSeriesLanguage, IProductSeriesLanguageSet)
2319
2420
25class ProductSeriesLanguage(RosettaStats):21class ProductSeriesLanguage(RosettaStats, TranslatedLanguageMixin):
26 """See `IProductSeriesLanguage`."""22 """See `IProductSeriesLanguage`."""
27 implements(IProductSeriesLanguage)23 implements(IProductSeriesLanguage)
2824
@@ -30,56 +26,14 @@
30 assert 'en' != language.code, (26 assert 'en' != language.code, (
31 'English is not a translatable language.')27 'English is not a translatable language.')
32 RosettaStats.__init__(self)28 RosettaStats.__init__(self)
29 TranslatedLanguageMixin.__init__(self)
33 self.productseries = productseries30 self.productseries = productseries
31 self.parent = productseries
34 self.language = language32 self.language = language
35 self.variant = variant33 self.variant = variant
36 self.pofile = pofile34 self.pofile = pofile
37 self.id = 035 self.id = 0
38 self._last_changed_date = None36 self.last_changed_date = None
39
40 # Reset all cached counts.
41 self.setCounts()
42
43 def setCounts(self, total=0, imported=0, changed=0, new=0,
44 unreviewed=0, last_changed=None):
45 """See `IProductSeriesLanguage`."""
46 self._messagecount = total
47 # "currentcount" in RosettaStats conflicts our recent terminology
48 # and is closer to "imported" (except that it doesn't include
49 # "changed") translations.
50 self._currentcount = imported
51 self._updatescount = changed
52 self._rosettacount = new
53 self._unreviewed_count = unreviewed
54 if last_changed is not None:
55 self._last_changed_date = last_changed
56
57 def _getMessageCount(self):
58 store = Store.of(self.language)
59 query = store.find(Sum(POTemplate.messagecount),
60 POTemplate.productseries==self.productseries,
61 POTemplate.iscurrent==True)
62 total, = query
63 if total is None:
64 total = 0
65 return total
66
67 def recalculateCounts(self):
68 """See `IProductSeriesLanguage`."""
69 store = Store.of(self.language)
70 query = store.find(
71 (Coalesce(Sum(POFile.currentcount), 0),
72 Coalesce(Sum(POFile.updatescount), 0),
73 Coalesce(Sum(POFile.rosettacount), 0),
74 Coalesce(Sum(POFile.unreviewed_count), 0)),
75 POFile.language==self.language,
76 POFile.variant==None,
77 POFile.potemplate==POTemplate.id,
78 POTemplate.productseries==self.productseries,
79 POTemplate.iscurrent==True)
80 imported, changed, new, unreviewed = query[0]
81 self.setCounts(self._getMessageCount(), imported, changed,
82 new, unreviewed)
8337
84 @property38 @property
85 def title(self):39 def title(self):
@@ -90,46 +44,29 @@
90 self.productseries.displayname)44 self.productseries.displayname)
9145
92 def messageCount(self):46 def messageCount(self):
93 """See `IProductSeriesLanguage`."""47 """See `IRosettaStats`."""
94 return self._messagecount48 return self._translation_statistics['total_count']
9549
96 def currentCount(self, language=None):50 def currentCount(self, language=None):
97 """See `IProductSeriesLanguage`."""51 """See `IRosettaStats`."""
98 return self._currentcount52 translated = self._translation_statistics['translated_count']
53 current = translated - self.rosettaCount(language)
54 return current
9955
100 def updatesCount(self, language=None):56 def updatesCount(self, language=None):
101 """See `IProductSeriesLanguage`."""57 """See `IRosettaStats`."""
102 return self._updatescount58 return self._translation_statistics['changed_count']
10359
104 def rosettaCount(self, language=None):60 def rosettaCount(self, language=None):
105 """See `IProductSeriesLanguage`."""61 """See `IRosettaStats`."""
106 return self._rosettacount62 new = self._translation_statistics['new_count']
63 changed = self._translation_statistics['changed_count']
64 rosetta = new + changed
65 return rosetta
10766
108 def unreviewedCount(self):67 def unreviewedCount(self):
109 """See `IProductSeriesLanguage`."""68 """See `IRosettaStats`."""
110 return self._unreviewed_count69 return self._translation_statistics['unreviewed_count']
111
112 @property
113 def last_changed_date(self):
114 """See `IProductSeriesLanguage`."""
115 return self._last_changed_date
116
117 @property
118 def pofiles(self):
119 """See `IProductSeriesLanguage`."""
120 store = Store.of(self.language)
121 result = store.find(
122 POFile,
123 POFile.language==self.language,
124 POFile.variant==self.variant,
125 POFile.potemplate==POTemplate.id,
126 POTemplate.productseries==self.productseries,
127 POTemplate.iscurrent==True)
128 return result.order_by(['-priority'])
129
130 def getPOFilesFor(self, potemplates):
131 """See `IProductSeriesLanguage`."""
132 return get_pofiles_for(potemplates, self.language, self.variant)
13370
13471
135class ProductSeriesLanguageSet:72class ProductSeriesLanguageSet:
13673
=== added file 'lib/lp/translations/model/translatedlanguage.py'
--- lib/lp/translations/model/translatedlanguage.py 1970-01-01 00:00:00 +0000
+++ lib/lp/translations/model/translatedlanguage.py 2010-07-23 12:49:25 +0000
@@ -0,0 +1,132 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4__all__ = ['TranslatedLanguageMixin']
5
6import pytz
7
8from zope.interface import implements
9
10from storm.expr import Coalesce, Desc, Max, Sum
11
12from lp.translations.interfaces.potemplate import IHasTranslationTemplates
13from lp.translations.interfaces.translatedlanguage import (
14 IPOFilesByPOTemplates, ITranslatedLanguage)
15from lp.translations.model.pofile import POFile
16from lp.translations.model.potemplate import POTemplate
17
18
19class POFilesByPOTemplates(object):
20 """See `IPOFilesByPOTemplates`."""
21 implements(IPOFilesByPOTemplates)
22
23 def __init__(self, templates_collection, language):
24 self.templates_collection = templates_collection
25 self.language = language
26
27 def _getDummyOrPOFile(self, potemplate, pofile):
28 if pofile is None:
29 return potemplate.getDummyPOFile(self.language,
30 check_for_existing=False)
31 else:
32 return pofile
33
34 def _getPOTemplatesAndPOFilesResultSet(self):
35 current_templates = self.templates_collection
36 pofiles = current_templates.joinOuterPOFile(self.language)
37 results = pofiles.select(POTemplate, POFile).order_by(
38 Desc(POTemplate.priority), POTemplate.name)
39 return results
40
41 def _getPOFilesForResultSet(self, resultset, selector=None):
42 pofiles_list = []
43 if selector is None:
44 results = resultset
45 else:
46 results = resultset[selector]
47 for potemplate, pofile in results:
48 pofiles_list.append(self._getDummyOrPOFile(potemplate, pofile))
49 return pofiles_list
50
51 def __getitem__(self, selector):
52 resultset = self._getPOTemplatesAndPOFilesResultSet()
53 if isinstance(selector, slice):
54 return self._getPOFilesForResultSet(resultset, selector)
55 else:
56 potemplate, pofile = resultset[selector]
57 return self._getDummyOrPOFile(potemplate, pofile)
58
59 def __iter__(self):
60 resultset = self._getPOTemplatesAndPOFilesResultSet()
61 for pofile in self._getPOFilesForResultSet(resultset):
62 yield pofile
63
64 def __len__(self):
65 return self.templates_collection.select(POTemplate).count()
66
67 def __nonzero__(self):
68 return bool(self.templates_collection.select(POTemplate).any())
69
70
71class TranslatedLanguageMixin(object):
72 """See `ITranslatedLanguage`."""
73 implements(ITranslatedLanguage)
74
75 language = None
76 parent = None
77
78 def __init__(self):
79 self.setCounts(total=0, translated=0, new=0, changed=0, unreviewed=0)
80
81 @property
82 def pofiles(self):
83 """See `ITranslatedLanguage`."""
84 assert IHasTranslationTemplates.providedBy(self.parent), (
85 "Parent object should implement `IHasTranslationTemplates`.")
86 current_templates = self.parent.getCurrentTemplatesCollection()
87 return POFilesByPOTemplates(current_templates, self.language)
88
89 @property
90 def translation_statistics(self):
91 """See `ITranslatedLanguage`."""
92 # This is a temporary translation statistics 'object' to allow
93 # smoother migration from IRosettaStats to something much nicer.
94 return self._translation_statistics
95
96 def setCounts(self, total, translated, new, changed, unreviewed):
97 """See `ITranslatedLanguage`."""
98 untranslated = total - translated
99 self._translation_statistics = {
100 'total_count': total,
101 'translated_count': translated,
102 'new_count': new,
103 'changed_count': changed,
104 'unreviewed_count': unreviewed,
105 'untranslated_count': untranslated,
106 }
107
108 def recalculateCounts(self):
109 """See `ITranslatedLanguage`."""
110 templates = self.parent.getCurrentTemplatesCollection()
111 pofiles = templates.joinOuterPOFile(self.language)
112 total_count_results = list(
113 pofiles.select(Coalesce(Sum(POTemplate.messagecount), 0),
114 Coalesce(Sum(POFile.currentcount), 0),
115 Coalesce(Sum(POFile.updatescount), 0),
116 Coalesce(Sum(POFile.rosettacount), 0),
117 Coalesce(Sum(POFile.unreviewed_count), 0),
118 Max(POFile.date_changed)))
119 total, imported, changed, rosetta, unreviewed, date_changed = (
120 total_count_results[0])
121 translated = imported + rosetta
122 new = rosetta - changed
123 self.setCounts(total, translated, new, changed, unreviewed)
124
125 # We have to add a timezone to the otherwise naive-datetime object
126 # (because we've gotten it using Max() aggregate function).
127 if date_changed is not None:
128 date_changed = date_changed.replace(tzinfo=pytz.UTC)
129 self.last_changed_date = date_changed
130
131 last_changed_date = None
132 last_translator = None
0133
=== modified file 'lib/lp/translations/stories/buildfarm/xx-build-summary.txt'
--- lib/lp/translations/stories/buildfarm/xx-build-summary.txt 2010-03-16 11:09:46 +0000
+++ lib/lp/translations/stories/buildfarm/xx-build-summary.txt 2010-07-23 12:49:25 +0000
@@ -14,6 +14,8 @@
14 ... ILibraryFileAliasSet)14 ... ILibraryFileAliasSet)
15 >>> from canonical.launchpad.scripts.logger import QuietFakeLogger15 >>> from canonical.launchpad.scripts.logger import QuietFakeLogger
16 >>> from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet16 >>> from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet
17 >>> from lp.testing.factory import (
18 ... remove_security_proxy_and_shout_at_engineer)
17 >>> from lp.testing.fakemethod import FakeMethod19 >>> from lp.testing.fakemethod import FakeMethod
18 >>> from lp.translations.interfaces.translations import (20 >>> from lp.translations.interfaces.translations import (
19 ... TranslationsBranchImportMode)21 ... TranslationsBranchImportMode)
@@ -29,12 +31,15 @@
2931
30 >>> productseries = factory.makeProductSeries(owner=owner)32 >>> productseries = factory.makeProductSeries(owner=owner)
31 >>> product = productseries.product33 >>> product = productseries.product
32 >>> product.official_rosetta = True34 >>> naked_product = remove_security_proxy_and_shout_at_engineer(product)
35 >>> naked_product.official_rosetta = True
33 >>> branch = factory.makeProductBranch(product=product, owner=owner)36 >>> branch = factory.makeProductBranch(product=product, owner=owner)
34 >>> branch_url = branch.unique_name37 >>> branch_url = branch.unique_name
3538
36 >>> productseries.branch = factory.makeBranch()39 >>> naked_productseries = remove_security_proxy_and_shout_at_engineer(
37 >>> productseries.translations_autoimport_mode = (40 ... productseries)
41 >>> naked_productseries.branch = factory.makeBranch()
42 >>> naked_productseries.translations_autoimport_mode = (
38 ... TranslationsBranchImportMode.IMPORT_TEMPLATES)43 ... TranslationsBranchImportMode.IMPORT_TEMPLATES)
39 >>> specific_job = factory.makeTranslationTemplatesBuildJob(branch=branch)44 >>> specific_job = factory.makeTranslationTemplatesBuildJob(branch=branch)
40 >>> buildqueue = getUtility(IBuildQueueSet).getByJob(specific_job.job)45 >>> buildqueue = getUtility(IBuildQueueSet).getByJob(specific_job.job)
4146
=== modified file 'lib/lp/translations/tests/test_productserieslanguage.py'
--- lib/lp/translations/tests/test_productserieslanguage.py 2010-07-21 09:35:41 +0000
+++ lib/lp/translations/tests/test_productserieslanguage.py 2010-07-23 12:49:25 +0000
@@ -173,8 +173,9 @@
173 self.productseries, self.language)173 self.productseries, self.language)
174 self.assertEquals(psl.messageCount(), 0)174 self.assertEquals(psl.messageCount(), 0)
175175
176 # So, we need to get it through productseries.productserieslanguages.176 # We explicitely ask for stats to be recalculated.
177 psl = self.productseries.productserieslanguages[0]177 psl.recalculateCounts()
178
178 self.assertPSLStatistics(psl,179 self.assertPSLStatistics(psl,
179 (pofile.messageCount(),180 (pofile.messageCount(),
180 pofile.translatedCount(),181 pofile.translatedCount(),
@@ -199,17 +200,14 @@
199 self.setPOFileStatistics(pofile2, 1, 1, 1, 1, pofile2.date_changed)200 self.setPOFileStatistics(pofile2, 1, 1, 1, 1, pofile2.date_changed)
200201
201 psl = self.productseries.productserieslanguages[0]202 psl = self.productseries.productserieslanguages[0]
202203 # We explicitely ask for stats to be recalculated.
203 # The psl.last_changed_date here is a naive datetime. So, for sake of204 psl.recalculateCounts()
204 # the tests, we should make pofile2 naive when checking if it matches
205 # the last calculated changed date, that should be the same as
206 # pofile2, created last.
207205
208 # Total is a sum of totals in both POTemplates (10+20).206 # Total is a sum of totals in both POTemplates (10+20).
209 # Translated is a sum of imported and rosetta translations,207 # Translated is a sum of imported and rosetta translations,
210 # which adds up as (4+3)+(1+1).208 # which adds up as (4+3)+(1+1).
211 self.assertPSLStatistics(psl, (30, 9, 5, 4, 3, 6,209 self.assertPSLStatistics(psl, (30, 9, 5, 4, 3, 6,
212 pofile2.date_changed.replace(tzinfo=None)))210 pofile2.date_changed))
213 self.assertPSLStatistics(psl, (211 self.assertPSLStatistics(psl, (
214 pofile1.messageCount() + pofile2.messageCount(),212 pofile1.messageCount() + pofile2.messageCount(),
215 pofile1.translatedCount() + pofile2.translatedCount(),213 pofile1.translatedCount() + pofile2.translatedCount(),
@@ -217,7 +215,7 @@
217 pofile1.rosettaCount() + pofile2.rosettaCount(),215 pofile1.rosettaCount() + pofile2.rosettaCount(),
218 pofile1.updatesCount() + pofile2.updatesCount(),216 pofile1.updatesCount() + pofile2.updatesCount(),
219 pofile1.unreviewedCount() + pofile2.unreviewedCount(),217 pofile1.unreviewedCount() + pofile2.unreviewedCount(),
220 pofile2.date_changed.replace(tzinfo=None)))218 pofile2.date_changed))
221219
222 def test_recalculateCounts(self):220 def test_recalculateCounts(self):
223 # Test that recalculateCounts works correctly.221 # Test that recalculateCounts works correctly.
@@ -236,13 +234,14 @@
236234
237 psl = self.psl_set.getProductSeriesLanguage(self.productseries,235 psl = self.psl_set.getProductSeriesLanguage(self.productseries,
238 self.language)236 self.language)
239 # recalculateCounts() doesn't recalculate the last changed date.237
240 psl.recalculateCounts()238 psl.recalculateCounts()
241 # Total is a sum of totals in both POTemplates (10+20).239 # Total is a sum of totals in both POTemplates (10+20).
242 # Translated is a sum of imported and rosetta translations,240 # Translated is a sum of imported and rosetta translations,
243 # which adds up as (1+3)+(1+1).241 # which adds up as (1+3)+(1+1).
242 # recalculateCounts() recalculates even the last changed date.
244 self.assertPSLStatistics(psl, (30, 6, 2, 4, 3, 5,243 self.assertPSLStatistics(psl, (30, 6, 2, 4, 3, 5,
245 None))244 pofile2.date_changed))
246245
247 def test_recalculateCounts_no_pofiles(self):246 def test_recalculateCounts_no_pofiles(self):
248 # Test that recalculateCounts works correctly even when there247 # Test that recalculateCounts works correctly even when there
249248
=== added file 'lib/lp/translations/tests/test_translatedlanguage.py'
--- lib/lp/translations/tests/test_translatedlanguage.py 1970-01-01 00:00:00 +0000
+++ lib/lp/translations/tests/test_translatedlanguage.py 2010-07-23 12:49:25 +0000
@@ -0,0 +1,462 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4__metaclass__ = type
5
6from zope.component import getUtility
7from zope.interface.verify import verifyObject
8from zope.security.proxy import removeSecurityProxy
9
10from lp.translations.interfaces.productserieslanguage import (
11 IProductSeriesLanguageSet)
12from lp.translations.interfaces.translatedlanguage import ITranslatedLanguage
13from lp.translations.model.pofile import DummyPOFile
14from lp.testing import TestCaseWithFactory
15from canonical.testing import ZopelessDatabaseLayer
16
17
18class TestTranslatedLanguageMixin(TestCaseWithFactory):
19 """Test TranslatedLanguageMixin."""
20
21 layer = ZopelessDatabaseLayer
22
23 def setUp(self):
24 # Create a productseries that uses translations.
25 TestCaseWithFactory.setUp(self)
26 self.productseries = self.factory.makeProductSeries()
27 self.productseries.product.official_rosetta = True
28 self.parent = self.productseries
29 self.psl_set = getUtility(IProductSeriesLanguageSet)
30 self.language = self.factory.makeLanguage('sr@test')
31
32 def getTranslatedLanguage(self, language):
33 return self.psl_set.getProductSeriesLanguage(self.productseries,
34 language)
35
36 def addPOTemplate(self, number_of_potmsgsets=0, priority=0):
37 potemplate = self.factory.makePOTemplate(
38 productseries=self.productseries)
39 for sequence in range(number_of_potmsgsets):
40 self.factory.makePOTMsgSet(potemplate, sequence=sequence+1)
41 removeSecurityProxy(potemplate).messagecount = number_of_potmsgsets
42 potemplate.priority = priority
43 return potemplate
44
45 def test_interface(self):
46 translated_language = self.getTranslatedLanguage(self.language)
47 self.assertTrue(verifyObject(ITranslatedLanguage,
48 translated_language))
49
50 def test_language(self):
51 translated_language = self.getTranslatedLanguage(self.language)
52 self.assertEqual(self.language,
53 translated_language.language)
54
55 def test_parent(self):
56 translated_language = self.getTranslatedLanguage(self.language)
57 self.assertEqual(self.parent,
58 translated_language.parent)
59
60 def test_pofiles_notemplates(self):
61 translated_language = self.getTranslatedLanguage(self.language)
62 self.assertEqual([], list(translated_language.pofiles))
63
64 def test_pofiles_template_no_pofiles(self):
65 translated_language = self.getTranslatedLanguage(self.language)
66 potemplate = self.addPOTemplate()
67 dummy_pofile = potemplate.getDummyPOFile(self.language)
68 pofiles = list(translated_language.pofiles)
69 self.assertEqual(1, len(pofiles))
70
71 # When there are no actual PO files, we get a DummyPOFile object
72 # instead.
73 dummy_pofile = pofiles[0]
74 naked_dummy = removeSecurityProxy(dummy_pofile)
75 self.assertEqual(DummyPOFile, type(naked_dummy))
76 self.assertEqual(self.language, dummy_pofile.language)
77 self.assertEqual(potemplate, dummy_pofile.potemplate)
78
79 # Two queries get executed when listifying
80 # TranslatedLanguageMixin.pofiles: a len() does a count, and
81 # then all POTemplates and POFiles are fetched with the other.
82 self.assertStatementCount(2, list, translated_language.pofiles)
83
84 def test_pofiles_template_with_pofiles(self):
85 translated_language = self.getTranslatedLanguage(self.language)
86 potemplate = self.addPOTemplate()
87 pofile = self.factory.makePOFile(self.language.code, potemplate)
88 self.assertEqual([pofile], list(translated_language.pofiles))
89
90 # Two queries get executed when listifying
91 # TranslatedLanguageMixin.pofiles: a len() does a count, and
92 # then all POTemplates and POFiles are fetched with the other.
93 self.assertStatementCount(2, list, translated_language.pofiles)
94
95 def test_pofiles_two_templates(self):
96 translated_language = self.getTranslatedLanguage(self.language)
97 # Two templates with different priorities so they get sorted
98 # appropriately.
99 potemplate1 = self.addPOTemplate(priority=2)
100 pofile1 = self.factory.makePOFile(self.language.code, potemplate1)
101 potemplate2 = self.addPOTemplate(priority=1)
102 pofile2 = self.factory.makePOFile(self.language.code, potemplate2)
103 self.assertEqual([pofile1, pofile2],
104 list(translated_language.pofiles))
105
106 # Two queries get executed when listifying
107 # TranslatedLanguageMixin.pofiles: a len() does a count, and
108 # then all POTemplates and POFiles are fetched with the other.
109 self.assertStatementCount(2, list, translated_language.pofiles)
110
111 def test_pofiles_two_templates_one_dummy(self):
112 translated_language = self.getTranslatedLanguage(self.language)
113 # Two templates with different priorities so they get sorted
114 # appropriately.
115 potemplate1 = self.addPOTemplate(priority=2)
116 pofile1 = self.factory.makePOFile(self.language.code, potemplate1)
117 potemplate2 = self.addPOTemplate(priority=1)
118 pofiles = translated_language.pofiles
119 self.assertEqual(pofile1, pofiles[0])
120 dummy_pofile = removeSecurityProxy(pofiles[1])
121 self.assertEqual(DummyPOFile, type(dummy_pofile))
122
123 # Two queries get executed when listifying
124 # TranslatedLanguageMixin.pofiles: a len() does a count, and
125 # then all POTemplates and POFiles are fetched with the other.
126 self.assertStatementCount(2, list, translated_language.pofiles)
127
128 def test_pofiles_slicing(self):
129 # Slicing still works, and always does the same constant number
130 # of queries (1).
131 translated_language = self.getTranslatedLanguage(self.language)
132 # Three templates with different priorities so they get sorted
133 # appropriately.
134 potemplate1 = self.addPOTemplate(priority=2)
135 pofile1 = self.factory.makePOFile(self.language.code, potemplate1)
136 potemplate2 = self.addPOTemplate(priority=1)
137 pofile2 = self.factory.makePOFile(self.language.code, potemplate2)
138 potemplate3 = self.addPOTemplate(priority=0)
139
140 pofiles = translated_language.pofiles[0:2]
141 self.assertEqual([pofile1, pofile2], list(pofiles))
142
143 # Slicing executes only a single query.
144 get_slice = lambda of, start, end: list(of[start:end])
145 self.assertStatementCount(1, get_slice,
146 translated_language.pofiles, 1, 3)
147
148 def test_pofiles_slicing_dummies(self):
149 # Slicing includes DummyPOFiles.
150 translated_language = self.getTranslatedLanguage(self.language)
151 # Three templates with different priorities so they get sorted
152 # appropriately.
153 potemplate1 = self.addPOTemplate(priority=2)
154 pofile1 = self.factory.makePOFile(self.language.code, potemplate1)
155 potemplate2 = self.addPOTemplate(priority=1)
156 pofile2 = self.factory.makePOFile(self.language.code, potemplate2)
157 potemplate3 = self.addPOTemplate(priority=0)
158
159 pofiles = translated_language.pofiles[1:3]
160 self.assertEqual(pofile2, pofiles[0])
161 dummy_pofile = removeSecurityProxy(pofiles[1])
162 self.assertEqual(DummyPOFile, type(dummy_pofile))
163
164 def test_statistics_empty(self):
165 translated_language = self.getTranslatedLanguage(self.language)
166
167 expected = {
168 'total_count': 0,
169 'translated_count': 0,
170 'new_count': 0,
171 'changed_count': 0,
172 'unreviewed_count': 0,
173 'untranslated_count': 0,
174 }
175 self.assertEqual(expected,
176 translated_language.translation_statistics)
177
178 def test_setCounts_statistics(self):
179 translated_language = self.getTranslatedLanguage(self.language)
180
181 total = 5
182 translated = 4
183 new = 3
184 changed = 2
185 unreviewed = 1
186 untranslated = total - translated
187
188 translated_language.setCounts(
189 total, translated, new, changed, unreviewed)
190
191 expected = {
192 'total_count': total,
193 'translated_count': translated,
194 'new_count': new,
195 'changed_count': changed,
196 'unreviewed_count': unreviewed,
197 'untranslated_count': untranslated,
198 }
199 self.assertEqual(expected,
200 translated_language.translation_statistics)
201
202 def test_recalculateCounts_empty(self):
203 translated_language = self.getTranslatedLanguage(self.language)
204
205 translated_language.recalculateCounts()
206
207 expected = {
208 'total_count': 0,
209 'translated_count': 0,
210 'new_count': 0,
211 'changed_count': 0,
212 'unreviewed_count': 0,
213 'untranslated_count': 0,
214 }
215 self.assertEqual(expected,
216 translated_language.translation_statistics)
217
218 def test_recalculateCounts_total_one_pofile(self):
219 translated_language = self.getTranslatedLanguage(self.language)
220 potemplate = self.addPOTemplate(number_of_potmsgsets=5)
221 pofile = self.factory.makePOFile(self.language.code, potemplate)
222
223 translated_language.recalculateCounts()
224 self.assertEqual(
225 5, translated_language.translation_statistics['total_count'])
226
227 def test_recalculateCounts_total_two_pofiles(self):
228 translated_language = self.getTranslatedLanguage(self.language)
229 potemplate1 = self.addPOTemplate(number_of_potmsgsets=5)
230 pofile1 = self.factory.makePOFile(self.language.code, potemplate1)
231 potemplate2 = self.addPOTemplate(number_of_potmsgsets=3)
232 pofile2 = self.factory.makePOFile(self.language.code, potemplate2)
233
234 translated_language.recalculateCounts()
235 self.assertEqual(
236 5+3, translated_language.translation_statistics['total_count'])
237
238 def test_recalculateCounts_translated_one_pofile(self):
239 translated_language = self.getTranslatedLanguage(self.language)
240 potemplate = self.addPOTemplate(number_of_potmsgsets=5)
241 pofile = self.factory.makePOFile(self.language.code, potemplate)
242 naked_pofile = removeSecurityProxy(pofile)
243 # translated count is current + rosetta
244 naked_pofile.currentcount = 3
245 naked_pofile.rosettacount = 1
246
247 translated_language.recalculateCounts()
248 self.assertEqual(
249 4, translated_language.translation_statistics['translated_count'])
250
251 def test_recalculateCounts_translated_two_pofiles(self):
252 translated_language = self.getTranslatedLanguage(self.language)
253 potemplate1 = self.addPOTemplate(number_of_potmsgsets=5)
254 pofile1 = self.factory.makePOFile(self.language.code, potemplate1)
255 naked_pofile1 = removeSecurityProxy(pofile1)
256 # translated count is current + rosetta
257 naked_pofile1.currentcount = 3
258 naked_pofile1.rosettacount = 1
259
260 potemplate2 = self.addPOTemplate(number_of_potmsgsets=3)
261 pofile2 = self.factory.makePOFile(self.language.code, potemplate2)
262 naked_pofile2 = removeSecurityProxy(pofile2)
263 # translated count is current + rosetta
264 naked_pofile2.currentcount = 1
265 naked_pofile2.rosettacount = 1
266
267 translated_language.recalculateCounts()
268 self.assertEqual(
269 6, translated_language.translation_statistics['translated_count'])
270
271 def test_recalculateCounts_changed_one_pofile(self):
272 translated_language = self.getTranslatedLanguage(self.language)
273 potemplate = self.addPOTemplate(number_of_potmsgsets=5)
274 pofile = self.factory.makePOFile(self.language.code, potemplate)
275 naked_pofile = removeSecurityProxy(pofile)
276 # translated count is current + rosetta
277 naked_pofile.updatescount = 3
278
279 translated_language.recalculateCounts()
280 self.assertEqual(
281 3, translated_language.translation_statistics['changed_count'])
282
283 def test_recalculateCounts_changed_two_pofiles(self):
284 translated_language = self.getTranslatedLanguage(self.language)
285 potemplate1 = self.addPOTemplate(number_of_potmsgsets=5)
286 pofile1 = self.factory.makePOFile(self.language.code, potemplate1)
287 naked_pofile1 = removeSecurityProxy(pofile1)
288 naked_pofile1.updatescount = 3
289
290 potemplate2 = self.addPOTemplate(number_of_potmsgsets=3)
291 pofile2 = self.factory.makePOFile(self.language.code, potemplate2)
292 naked_pofile2 = removeSecurityProxy(pofile2)
293 naked_pofile2.updatescount = 1
294
295 translated_language.recalculateCounts()
296 self.assertEqual(
297 4, translated_language.translation_statistics['changed_count'])
298
299 def test_recalculateCounts_new_one_pofile(self):
300 translated_language = self.getTranslatedLanguage(self.language)
301 potemplate = self.addPOTemplate(number_of_potmsgsets=5)
302 pofile = self.factory.makePOFile(self.language.code, potemplate)
303 naked_pofile = removeSecurityProxy(pofile)
304 # new count is rosetta - changed
305 naked_pofile.rosettacount = 3
306 naked_pofile.updatescount = 1
307
308 translated_language.recalculateCounts()
309 self.assertEqual(
310 2, translated_language.translation_statistics['new_count'])
311
312 def test_recalculateCounts_new_two_pofiles(self):
313 translated_language = self.getTranslatedLanguage(self.language)
314 potemplate1 = self.addPOTemplate(number_of_potmsgsets=5)
315 pofile1 = self.factory.makePOFile(self.language.code, potemplate1)
316 naked_pofile1 = removeSecurityProxy(pofile1)
317 # new count is rosetta - changed
318 naked_pofile1.rosettacount = 3
319 naked_pofile1.updatescount = 1
320
321 potemplate2 = self.addPOTemplate(number_of_potmsgsets=3)
322 pofile2 = self.factory.makePOFile(self.language.code, potemplate2)
323 naked_pofile2 = removeSecurityProxy(pofile2)
324 # new count is rosetta - changed
325 naked_pofile2.rosettacount = 2
326 naked_pofile2.updatescount = 1
327
328 translated_language.recalculateCounts()
329 self.assertEqual(
330 3, translated_language.translation_statistics['new_count'])
331
332 def test_recalculateCounts_unreviewed_one_pofile(self):
333 translated_language = self.getTranslatedLanguage(self.language)
334 potemplate = self.addPOTemplate(number_of_potmsgsets=5)
335 pofile = self.factory.makePOFile(self.language.code, potemplate)
336 naked_pofile = removeSecurityProxy(pofile)
337 # translated count is current + rosetta
338 naked_pofile.unreviewed_count = 3
339
340 translated_language.recalculateCounts()
341 self.assertEqual(
342 3, translated_language.translation_statistics['unreviewed_count'])
343
344 def test_recalculateCounts_unreviewed_two_pofiles(self):
345 translated_language = self.getTranslatedLanguage(self.language)
346 potemplate1 = self.addPOTemplate(number_of_potmsgsets=5)
347 pofile1 = self.factory.makePOFile(self.language.code, potemplate1)
348 naked_pofile1 = removeSecurityProxy(pofile1)
349 naked_pofile1.unreviewed_count = 3
350
351 potemplate2 = self.addPOTemplate(number_of_potmsgsets=3)
352 pofile2 = self.factory.makePOFile(self.language.code, potemplate2)
353 naked_pofile2 = removeSecurityProxy(pofile2)
354 naked_pofile2.unreviewed_count = 1
355
356 translated_language.recalculateCounts()
357 self.assertEqual(
358 4, translated_language.translation_statistics['unreviewed_count'])
359
360 def test_recalculateCounts_one_pofile(self):
361 translated_language = self.getTranslatedLanguage(self.language)
362 potemplate = self.addPOTemplate(number_of_potmsgsets=5)
363 pofile = self.factory.makePOFile(self.language.code, potemplate)
364 naked_pofile = removeSecurityProxy(pofile)
365 # translated count is current + rosetta
366 naked_pofile.currentcount = 3
367 naked_pofile.rosettacount = 1
368 # Changed count is 'updatescount' on POFile.
369 # It has to be lower or equal to currentcount.
370 naked_pofile.updatescount = 1
371 # new is rosettacount-updatescount.
372 naked_pofile.newcount = 0
373 naked_pofile.unreviewed_count = 3
374
375 translated_language.recalculateCounts()
376
377 expected = {
378 'total_count': 5,
379 'translated_count': 4,
380 'new_count': 0,
381 'changed_count': 1,
382 'unreviewed_count': 3,
383 'untranslated_count': 1,
384 }
385 self.assertEqual(expected,
386 translated_language.translation_statistics)
387
388 def test_recalculateCounts_two_pofiles(self):
389 translated_language = self.getTranslatedLanguage(self.language)
390
391 # Set up one template with a single PO file.
392 potemplate1 = self.addPOTemplate(number_of_potmsgsets=5)
393 pofile1 = self.factory.makePOFile(self.language.code, potemplate1)
394 naked_pofile1 = removeSecurityProxy(pofile1)
395 # translated count is current + rosetta
396 naked_pofile1.currentcount = 2
397 naked_pofile1.rosettacount = 2
398 # Changed count is 'updatescount' on POFile.
399 # It has to be lower or equal to currentcount.
400 # new is rosettacount-updatescount.
401 naked_pofile1.updatescount = 1
402 naked_pofile1.unreviewed_count = 3
403
404 # Set up second template with a single PO file.
405 potemplate2 = self.addPOTemplate(number_of_potmsgsets=3)
406 pofile2 = self.factory.makePOFile(self.language.code, potemplate2)
407 naked_pofile2 = removeSecurityProxy(pofile2)
408 # translated count is current + rosetta
409 naked_pofile2.currentcount = 1
410 naked_pofile2.rosettacount = 2
411 # Changed count is 'updatescount' on POFile.
412 # It has to be lower or equal to currentcount.
413 # new is rosettacount-updatescount.
414 naked_pofile2.updatescount = 1
415 naked_pofile2.unreviewed_count = 1
416
417 translated_language.recalculateCounts()
418
419 expected = {
420 'total_count': 8,
421 'translated_count': 7,
422 'new_count': 2,
423 'changed_count': 2,
424 'unreviewed_count': 4,
425 'untranslated_count': 1,
426 }
427 self.assertEqual(expected,
428 translated_language.translation_statistics)
429
430 def test_recalculateCounts_two_templates_one_translation(self):
431 # Make sure recalculateCounts works even if a POFile is missing
432 # for one of the templates.
433 translated_language = self.getTranslatedLanguage(self.language)
434
435 # Set up one template with a single PO file.
436 potemplate1 = self.addPOTemplate(number_of_potmsgsets=5)
437 pofile1 = self.factory.makePOFile(self.language.code, potemplate1)
438 naked_pofile1 = removeSecurityProxy(pofile1)
439 # translated count is current + rosetta
440 naked_pofile1.currentcount = 2
441 naked_pofile1.rosettacount = 2
442 # Changed count is 'updatescount' on POFile.
443 # It has to be lower or equal to currentcount.
444 # new is rosettacount-updatescount.
445 naked_pofile1.updatescount = 1
446 naked_pofile1.unreviewed_count = 3
447
448 # Set up second template with a single PO file.
449 potemplate2 = self.addPOTemplate(number_of_potmsgsets=3)
450
451 translated_language.recalculateCounts()
452
453 expected = {
454 'total_count': 8,
455 'translated_count': 4,
456 'new_count': 1,
457 'changed_count': 1,
458 'unreviewed_count': 3,
459 'untranslated_count': 4,
460 }
461 self.assertEqual(expected,
462 translated_language.translation_statistics)
0463
=== modified file 'lib/lp/translations/tests/test_translationtemplatescollection.py'
--- lib/lp/translations/tests/test_translationtemplatescollection.py 2010-07-17 16:19:38 +0000
+++ lib/lp/translations/tests/test_translationtemplatescollection.py 2010-07-23 12:49:25 +0000
@@ -205,3 +205,22 @@
205 ]205 ]
206 self.assertContentEqual(206 self.assertContentEqual(
207 expected_outcome, joined.select(POTemplate, POFile))207 expected_outcome, joined.select(POTemplate, POFile))
208
209 def test_joinOuterPOFile_language(self):
210 trunk = self.factory.makeProduct().getSeries('trunk')
211 translated_template = self.factory.makePOTemplate(productseries=trunk)
212 untranslated_template = self.factory.makePOTemplate(
213 productseries=trunk)
214 nl = translated_template.newPOFile('nl')
215 de = translated_template.newPOFile('de')
216
217 collection = TranslationTemplatesCollection()
218 by_series = collection.restrictProductSeries(trunk)
219 joined = by_series.joinOuterPOFile(language=nl.language)
220
221 expected_outcome = [
222 (translated_template, nl),
223 (untranslated_template, None),
224 ]
225 self.assertContentEqual(
226 expected_outcome, joined.select(POTemplate, POFile))
208227
=== modified file 'utilities/make-lp-user'
--- utilities/make-lp-user 2010-04-27 19:48:39 +0000
+++ utilities/make-lp-user 2010-07-23 12:49:25 +0000
@@ -46,7 +46,6 @@
46from canonical.launchpad.interfaces import (46from canonical.launchpad.interfaces import (
47 IPersonSet,47 IPersonSet,
48 ISSHKeySet,48 ISSHKeySet,
49 SSHKeyType,
50 TeamMembershipStatus,49 TeamMembershipStatus,
51 )50 )
52from canonical.launchpad.interfaces.gpghandler import IGPGHandler51from canonical.launchpad.interfaces.gpghandler import IGPGHandler
@@ -111,8 +110,8 @@
111 ssh_dir = os.path.expanduser('~/.ssh')110 ssh_dir = os.path.expanduser('~/.ssh')
112 key_set = getUtility(ISSHKeySet)111 key_set = getUtility(ISSHKeySet)
113 key_guesses = [112 key_guesses = [
114 (SSHKeyType.RSA, 'id_rsa.pub'),113 ("ssh-rsa", 'id_rsa.pub'),
115 (SSHKeyType.DSA, 'id_dsa.pub'),114 ("ssh-dsa", 'id_dsa.pub'),
116 ]115 ]
117 for key_type, guessed_filename in key_guesses:116 for key_type, guessed_filename in key_guesses:
118 guessed_filename = os.path.join(ssh_dir, guessed_filename)117 guessed_filename = os.path.join(ssh_dir, guessed_filename)
@@ -125,7 +124,8 @@
125 except (OSError, IOError):124 except (OSError, IOError):
126 continue125 continue
127 public_key = public_key.split()[1]126 public_key = public_key.split()[1]
128 key_set.new(person, key_type, public_key, 'Added by utility script.')127 key_set.new(person,
128 "%s %s %s" % (key_type, public_key, 'Added by utility script.'))
129 print 'Registered SSH key: %s' % (guessed_filename,)129 print 'Registered SSH key: %s' % (guessed_filename,)
130130
131131

Subscribers

People subscribed via source and target branches

to status/vote changes: