Merge lp:~adiroiban/launchpad/bug-462013-try-2 into lp:launchpad

Proposed by Adi Roiban
Status: Merged
Approved by: Michael Nelson
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~adiroiban/launchpad/bug-462013-try-2
Merge into: lp:launchpad
Diff against target: 210 lines (+128/-18)
3 files modified
lib/lp/translations/browser/product.py (+12/-5)
lib/lp/translations/browser/tests/test_product_view.py (+58/-12)
lib/lp/translations/stories/productseries/xx-productseries-translations.txt (+58/-1)
To merge this branch: bzr merge lp:~adiroiban/launchpad/bug-462013-try-2
Reviewer Review Type Date Requested Status
Michael Nelson (community) code Approve
Review via email: mp+18186@code.launchpad.net

Commit message

Don't include series in the list of series that can be set up for translations.

To post a comment you must log in.
Revision history for this message
Adi Roiban (adiroiban) wrote :

= Bug 462013 =
On a project's main translation page, there is a little box ("Set up translations for other series") that suggests I import templates for any series in my project that isn't yet imported. But it shouldn't bother listing obsolete series.

== Proposed fix ==
Don't include series with status OBSOLETE in the prompt list of series that can be set up for translations

== Pre-implementation notes ==

This was initially branch was submitted together with DistroSeries to Series refactoring (bug 496352).
Danilo took a look and decided to split the branch in refactoring and this new branch.

== Implementation details ==

The test from xx-productseries-translations.txt , line 150 was setting the status for 'evolution' but then the test were done for 'bazaar'.
I've changed the code to set the status bazaar.

The series are listed in random order. This is why I used `u'serie name' in extracted_text`.

== Tests ==
lp-test -t productseries-translations

== Demo and Q/A ==

In a product, create 2 new series, one in developlment and the other obsolete.
Go to product translation page. ie: http://translations.launchpad.dev/evolution

The obsolete series should not appear in the 'Set up translations for a series' list.

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/lp/translations/browser/product.py
  lib/lp/translations/stories/productseries/xx-productseries-translations.txt

Revision history for this message
Michael Nelson (michael.nelson) wrote :

<noodles775> adiroiban: I can take a look at that branch, or did you want someone specific to look at it?
<adiroiban> noodles775: hi. nope. you can review it
<noodles775> adiroiban: hi, your new test seems to pass even when I revert the change to product.py?
<noodles775> (BTW: thanks for adding extra tests too!)
<adiroiban> noodles775: uh... yes. I forget to test „obsolete is _not_ in extracted_text”
<adiroiban> noodles775: I pushed the missing test
<noodles775> adiroiban: thanks... I'm actually looking at the pagetest, and thinking that most of it is really testing the *view* functionality and should really be in a view/browser test...
<noodles775> I mean, you're really testing the untranslatable_series property... what do you think?
<adiroiban> noodles775: yes. I will move them in browser/tests
<noodles775> adiroiban: So perhaps just add one series in the pagetest and verify that it appears, then yes, the rest can go in the view test.
<adiroiban> noodles775: or 2...one active and one obsolete
<noodles775> adiroiban: also, I (personally) think it would be worth sorting the results of untranslatable_series - random order is not good. What do you think?
<noodles775> adiroiban: well, the fact that the obsolete series won't be included should be in the view test (as it's view code).
<adiroiban> noodles775: ok. alphabetically sorted?
<noodles775> adiroiban: if that make the most sense, yes (another option would be by the status, but you'll know more about which makes most sense :)
<noodles775> adiroiban: So I'll make this as needs-fixing for the moment. Just ping me when you're done and I'll go through the code in detail. Thanks!
<adiroiban> noodles775: ok. thanks

review: Needs Fixing (code)
Revision history for this message
Adi Roiban (adiroiban) wrote :
Download full text (11.1 KiB)

Hi Michael,

I have moved the full test to unit testing layer and listed the series sorted alphabetically.

When you have time, please take a look at the latest diff. No hurry. Thanks!

=== modified file 'lib/lp/translations/browser/product.py'
--- lib/lp/translations/browser/product.py 2010-01-28 09:11:58 +0000
+++ lib/lp/translations/browser/product.py 2010-01-28 14:21:11 +0000
@@ -85,7 +85,7 @@
     @cachedproperty
     def uses_translations(self):
         """Whether this product has translatable templates."""
- return (self.context.official_rosetta and
+ return (self.context.official_rosetta and
                 self.primary_translatable is not None)

     @cachedproperty
@@ -114,13 +114,15 @@
     @cachedproperty
     def untranslatable_series(self):
         """Return series which are not yet set up for translations.
-
- Obsolete series will be excluded from this list.
+
+ The list is sorted in alphabetically order and obsolete series
+ will be excluded.
         """

         all_active_series = set(
             [serie for serie in self.context.series if (
                 serie.status != SeriesStatus.OBSOLETE)])
         translatable = set(self.context.translatable_series)
- return all_active_series - translatable
-
+ return sorted(
+ all_active_series - translatable,
+ key=lambda series: series.name)

=== modified file 'lib/lp/translations/browser/tests/test_product_view.py'
--- lib/lp/translations/browser/tests/test_product_view.py 2010-01-16 08:45:58 +0000
+++ lib/lp/translations/browser/tests/test_product_view.py 2010-01-28 14:20:51 +0000
@@ -9,6 +9,7 @@
 from canonical.testing import LaunchpadZopelessLayer
 from lp.translations.browser.product import ProductView
 from lp.testing import TestCaseWithFactory
+from lp.registry.interfaces.series import SeriesStatus

 class TestProduct(TestCaseWithFactory):
@@ -17,27 +18,82 @@
     layer = LaunchpadZopelessLayer

     def setUp(self):
- # Create a product that uses translations.
         TestCaseWithFactory.setUp(self)
- self.product = self.factory.makeProduct()
- self.series = self.product.development_focus
- self.product.official_rosetta = True
- self.view = ProductView(self.product,
- LaunchpadTestRequest())

     def test_primary_translatable_with_package_link(self):
+ # Create a product that uses translations.
+ product = self.factory.makeProduct()
+ series = product.development_focus
+ product.official_rosetta = True
+ view = ProductView(product, LaunchpadTestRequest())
+
         # If development focus series is linked to
         # a distribution package with translations,
         # we do not try to show translation statistics
         # for the package.
         sourcepackage = self.factory.makeSourcePackage()
- sourcepackage.setPackaging(self.series, None)
+ sourcepackage.setPackaging(series, None)
         sourcepackage.distroseries.distribution.official_rosetta = True
         pot = self.factory.makePOTemplate(
             distroseries=sourcepackage.distroseries,
       ...

Revision history for this message
Michael Nelson (michael.nelson) wrote :
Download full text (11.8 KiB)

Hi Adi,

Thanks for moving those tests to a unit-test, you'll make a lot of people happy :) (We're trying to keep documentation nice and readable, with all non-documentation-worthy tests moved to unit tests).

I think you've got everything in the right place, but I do have a few suggestions below for simplifying the implementation and test a bit. I'll mark this as approved, but I'm keen to see which of the suggestions below you agree or disagree with.

Cheers,
Michael

> === modified file 'lib/lp/translations/browser/product.py'
> --- lib/lp/translations/browser/product.py 2010-01-22 18:10:53 +0000
> +++ lib/lp/translations/browser/product.py 2010-01-28 15:26:22 +0000
> @@ -18,6 +18,7 @@
> from canonical.launchpad.webapp.menu import NavigationMenu
> from lp.registry.interfaces.product import IProduct
> from lp.registry.interfaces.productseries import IProductSeries
> +from lp.registry.interfaces.series import SeriesStatus
> from lp.registry.browser.product import ProductEditView
> from lp.translations.browser.translations import TranslationsMixin
>
> @@ -84,7 +85,7 @@
> @cachedproperty
> def uses_translations(self):
> """Whether this product has translatable templates."""
> - return (self.context.official_rosetta and
> + return (self.context.official_rosetta and

Thanks for the drive-by!

> self.primary_translatable is not None)
>
> @cachedproperty
> @@ -112,7 +113,16 @@
>
> @cachedproperty
> def untranslatable_series(self):
> - """Return series which are not yet set up for translations."""
> - all_series = set(self.context.series)
> + """Return series which are not yet set up for translations.
> +
> + The list is sorted in alphabetically order and obsolete series
> + will be excluded.

s/will be/are ?

> + """
> +
> + all_active_series = set(
> + [serie for serie in self.context.series if (
> + serie.status != SeriesStatus.OBSOLETE)])

It's pretty confusing, but grepping through the code a bit shows that
we seem to have a precedent of:

series for series in self.context.series

Take a look and see what you think.

> translatable = set(self.context.translatable_series)
> - return all_series - translatable
> + return sorted(
> + all_active_series - translatable,
> + key=lambda series: series.name)

We're meant to avoid using lambda (search for it in
https://dev.launchpad.net/PythonStyleGuide). The guide has an alternative
using attrgetter for your specific use-case here.

But that said, I wonder if this could be simpler, given that IProduct.series
already orders by name? I've just tested the following which also works:

    def untranslatable_series(self):
 """..."""
        translatable = self.context.translatable_series
        return [series for series in self.context.series if (
            series.status != SeriesStatus.OBSOLETE and
            series not in translatable)]

Let me know if there was a specific reason for converting to sets - given
that we're iterating anyway for the list comprehension, I'm not sure
there's a benefit in using the...

review: Approve (code)
Revision history for this message
Adi Roiban (adiroiban) wrote :
Download full text (4.1 KiB)

Hey,

Thanks for the review.
Have no mercy reviewing my branches. It is your project and you should not accept bad code.
Don't worry, I will not take offence.

Here is the latest diff.

=== modified file 'lib/lp/translations/browser/product.py'
--- lib/lp/translations/browser/product.py 2010-01-28 14:23:10 +0000
+++ lib/lp/translations/browser/product.py 2010-01-29 16:08:54 +0000
@@ -116,13 +116,10 @@
         """Return series which are not yet set up for translations.

         The list is sorted in alphabetically order and obsolete series
- will be excluded.
+ are excluded.
         """

- all_active_series = set(
- [serie for serie in self.context.series if (
- serie.status != SeriesStatus.OBSOLETE)])
- translatable = set(self.context.translatable_series)
- return sorted(
- all_active_series - translatable,
- key=lambda series: series.name)
+ translatable = self.context.translatable_series
+ return [series for series in self.context.series if (
+ series.status != SeriesStatus.OBSOLETE and
+ series not in translatable)]

=== modified file 'lib/lp/translations/browser/tests/test_product_view.py'
--- lib/lp/translations/browser/tests/test_product_view.py 2010-01-28 14:23:10 +0000
+++ lib/lp/translations/browser/tests/test_product_view.py 2010-01-29 16:11:36 +0000
@@ -7,9 +7,9 @@

 from canonical.launchpad.webapp.servers import LaunchpadTestRequest
 from canonical.testing import LaunchpadZopelessLayer
+from lp.registry.interfaces.series import SeriesStatus
 from lp.translations.browser.product import ProductView
 from lp.testing import TestCaseWithFactory
-from lp.registry.interfaces.series import SeriesStatus

 class TestProduct(TestCaseWithFactory):
@@ -17,9 +17,6 @@

     layer = LaunchpadZopelessLayer

- def setUp(self):
- TestCaseWithFactory.setUp(self)
-
     def test_primary_translatable_with_package_link(self):
         # Create a product that uses translations.
         product = self.factory.makeProduct()
@@ -75,24 +72,17 @@
             product=product, name='evo-future')
         series_future.status = SeriesStatus.FUTURE

- # 'untranslatable_series' is a cached property, this is why we
- # check it after adding all series.
- self.assertTrue(series_experimental in view.untranslatable_series)
- self.assertTrue(series_development in view.untranslatable_series)
- self.assertTrue(series_frozen in view.untranslatable_series)
- self.assertTrue(series_current in view.untranslatable_series)
- self.assertTrue(series_supported in view.untranslatable_series)
- self.assertTrue(series_future in view.untranslatable_series)
-
- # Obsolete series are not included
- self.assertFalse(series_obsolete in view.untranslatable_series)
-
- # Series are listed in alphabetical order, 'evo-current'
- # is firsts, while 'trunk' is last.
- self.assertTrue(
- series_current == view.untranslatable_series[0])
- self.assertTrue(
- series_trunk == view.untranslatable_series[-1])
+ # The series are return...

Read more...

Revision history for this message
Michael Nelson (michael.nelson) wrote :

Thanks Adi. Looks good. Still r=me.

Revision history for this message
Adi Roiban (adiroiban) wrote :

Thanks.
Do you have time to land this branch or should I ask someone else?

Cheers,
Adi

Revision history for this message
Michael Nelson (michael.nelson) wrote :

On Tue, Feb 2, 2010 at 4:35 PM, Adi Roiban <email address hidden> wrote:
> Thanks.
> Do you have time to land this branch or should I ask someone else?

Sure. It's on ec2 to land now :)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/translations/browser/product.py'
2--- lib/lp/translations/browser/product.py 2010-01-23 21:42:36 +0000
3+++ lib/lp/translations/browser/product.py 2010-01-29 16:28:23 +0000
4@@ -18,6 +18,7 @@
5 from canonical.launchpad.webapp.menu import NavigationMenu
6 from lp.registry.interfaces.product import IProduct
7 from lp.registry.interfaces.productseries import IProductSeries
8+from lp.registry.interfaces.series import SeriesStatus
9 from lp.registry.browser.product import ProductEditView
10 from lp.translations.browser.translations import TranslationsMixin
11
12@@ -88,7 +89,7 @@
13 @cachedproperty
14 def uses_translations(self):
15 """Whether this product has translatable templates."""
16- return (self.context.official_rosetta and
17+ return (self.context.official_rosetta and
18 self.primary_translatable is not None)
19
20 @cachedproperty
21@@ -116,7 +117,13 @@
22
23 @cachedproperty
24 def untranslatable_series(self):
25- """Return series which are not yet set up for translations."""
26- all_series = set(self.context.series)
27- translatable = set(self.context.translatable_series)
28- return all_series - translatable
29+ """Return series which are not yet set up for translations.
30+
31+ The list is sorted in alphabetically order and obsolete series
32+ are excluded.
33+ """
34+
35+ translatable = self.context.translatable_series
36+ return [series for series in self.context.series if (
37+ series.status != SeriesStatus.OBSOLETE and
38+ series not in translatable)]
39
40=== modified file 'lib/lp/translations/browser/tests/test_product_view.py'
41--- lib/lp/translations/browser/tests/test_product_view.py 2010-01-16 08:45:58 +0000
42+++ lib/lp/translations/browser/tests/test_product_view.py 2010-01-29 16:28:23 +0000
43@@ -7,6 +7,7 @@
44
45 from canonical.launchpad.webapp.servers import LaunchpadTestRequest
46 from canonical.testing import LaunchpadZopelessLayer
47+from lp.registry.interfaces.series import SeriesStatus
48 from lp.translations.browser.product import ProductView
49 from lp.testing import TestCaseWithFactory
50
51@@ -16,28 +17,73 @@
52
53 layer = LaunchpadZopelessLayer
54
55- def setUp(self):
56- # Create a product that uses translations.
57- TestCaseWithFactory.setUp(self)
58- self.product = self.factory.makeProduct()
59- self.series = self.product.development_focus
60- self.product.official_rosetta = True
61- self.view = ProductView(self.product,
62- LaunchpadTestRequest())
63-
64 def test_primary_translatable_with_package_link(self):
65+ # Create a product that uses translations.
66+ product = self.factory.makeProduct()
67+ series = product.development_focus
68+ product.official_rosetta = True
69+ view = ProductView(product, LaunchpadTestRequest())
70+
71 # If development focus series is linked to
72 # a distribution package with translations,
73 # we do not try to show translation statistics
74 # for the package.
75 sourcepackage = self.factory.makeSourcePackage()
76- sourcepackage.setPackaging(self.series, None)
77+ sourcepackage.setPackaging(series, None)
78 sourcepackage.distroseries.distribution.official_rosetta = True
79 pot = self.factory.makePOTemplate(
80 distroseries=sourcepackage.distroseries,
81 sourcepackagename=sourcepackage.sourcepackagename)
82- self.assertEquals(None, self.view.primary_translatable)
83+ self.assertEquals(None, view.primary_translatable)
84+
85+ def test_untranslatable_series(self):
86+ # Create a product that uses translations.
87+ product = self.factory.makeProduct()
88+ series_trunk = product.development_focus
89+ product.official_rosetta = True
90+ view = ProductView(product, LaunchpadTestRequest())
91+
92+ # New series are added, one for each type of status
93+ series_experimental = self.factory.makeProductSeries(
94+ product=product, name='evo-experimental')
95+ series_experimental.status = SeriesStatus.EXPERIMENTAL
96+
97+ series_development = self.factory.makeProductSeries(
98+ product=product, name='evo-development')
99+ series_development.status = SeriesStatus.DEVELOPMENT
100+
101+ series_frozen = self.factory.makeProductSeries(
102+ product=product, name='evo-frozen')
103+ series_frozen.status = SeriesStatus.FROZEN
104+
105+ series_current = self.factory.makeProductSeries(
106+ product=product, name='evo-current')
107+ series_current.status = SeriesStatus.CURRENT
108+
109+ series_supported = self.factory.makeProductSeries(
110+ product=product, name='evo-supported')
111+ series_supported.status = SeriesStatus.SUPPORTED
112+
113+ series_obsolete = self.factory.makeProductSeries(
114+ product=product, name='evo-obsolete')
115+ series_obsolete.status = SeriesStatus.OBSOLETE
116+
117+ series_future = self.factory.makeProductSeries(
118+ product=product, name='evo-future')
119+ series_future.status = SeriesStatus.FUTURE
120+
121+ # The series are returned in alphabetical order and do not
122+ # include obsolete series.
123+ series_names = [series.name for series in view.untranslatable_series]
124+ self.assertEqual([
125+ u'evo-current',
126+ u'evo-development',
127+ u'evo-experimental',
128+ u'evo-frozen',
129+ u'evo-future',
130+ u'evo-supported',
131+ u'trunk'], series_names)
132+
133
134 def test_suite():
135 return unittest.TestLoader().loadTestsFromName(__name__)
136-
137
138=== modified file 'lib/lp/translations/stories/productseries/xx-productseries-translations.txt'
139--- lib/lp/translations/stories/productseries/xx-productseries-translations.txt 2010-01-05 16:19:13 +0000
140+++ lib/lp/translations/stories/productseries/xx-productseries-translations.txt 2010-01-29 16:28:23 +0000
141@@ -148,7 +148,7 @@
142
143 # Use the raw DB object to bypass the security proxy.
144 >>> from lp.registry.model.product import Product
145- >>> Product.byName('evolution').official_rosetta = False
146+ >>> Product.byName('bazaar').official_rosetta = False
147
148 When the owner now visits the upload page for trunk, there's a notice.
149
150@@ -268,3 +268,60 @@
151 >>> print extract_text(
152 ... find_tag_by_id(admin_browser.contents, 'translation-focus'))
153 Launchpad currently recommends translating 1.6.
154+
155+
156+Setting up translations for series
157+----------------------------------
158+
159+When visiting product translations main page, project developers sees
160+status for current series configured for translations.
161+Beside the "All translatable series" section, they will find the
162+"Set up translations for a series" section with links to other series
163+that can be configured for translations.
164+
165+When projects have only one active series, and it is already configured,
166+project admin does not see the link for configuring other branches.
167+
168+ >>> admin_browser.open(
169+ ... 'http://translations.launchpad.dev/evolution')
170+ >>> untranslatable = find_tag_by_id(
171+ ... admin_browser.contents, 'portlet-untranslatable-branches')
172+ >>> untranslatable is None
173+ True
174+
175+A new series is added.
176+
177+ >>> from lp.registry.model.product import Product
178+ >>> from lp.registry.interfaces.series import SeriesStatus
179+ >>> login('foo.bar@canonical.com')
180+ >>> evolution = Product.byName('evolution')
181+ >>> series = factory.makeProductSeries(
182+ ... product=evolution, name='evo-new')
183+ >>> series.status = SeriesStatus.EXPERIMENTAL
184+ >>> logout()
185+
186+Project administrator will see links to configuring translations for
187+the new series.
188+
189+ >>> admin_browser.open(
190+ ... 'http://translations.launchpad.dev/evolution')
191+ >>> untranslatable = find_tag_by_id(
192+ ... admin_browser.contents, 'portlet-untranslatable-branches')
193+ >>> print extract_text(untranslatable)
194+ Set up translations for a series...
195+ evo-new series — manual or automatic...
196+
197+For each series there is a link for accesing the series translations
198+page togheter with link for uploading a template from that series
199+(manual) and setting automatic imports.
200+
201+ >>> print admin_browser.getLink('Evolution evo-new series').url
202+ http://translations.launchpad.dev/evolution/evo-new/+translations
203+
204+ >>> print admin_browser.getLink(
205+ ... 'manual', url='/evolution/evo-new/+translations-upload').url
206+ http://translations.launchpad.dev/evolution/evo-new/+translations-upload
207+
208+ >>> print admin_browser.getLink(
209+ ... 'automatic', url='/evolution/evo-new/+translations-settings').url
210+ http://translations.launchpad.dev/evolution/evo-new/+translations-settings