Merge lp:~adiroiban/launchpad/bug-427319 into lp:launchpad
- bug-427319
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Guilherme Salgado |
Approved revision: | no longer in the source branch. |
Merged at revision: | not available |
Proposed branch: | lp:~adiroiban/launchpad/bug-427319 |
Merge into: | lp:launchpad |
Diff against target: |
619 lines (+365/-103) 7 files modified
lib/lp/translations/browser/configure.zcml (+5/-6) lib/lp/translations/browser/productserieslanguage.py (+0/-57) lib/lp/translations/browser/serieslanguage.py (+124/-10) lib/lp/translations/browser/tests/test_distroserieslanguage_views.py (+5/-1) lib/lp/translations/browser/tests/test_productserieslanguage_views.py (+3/-1) lib/lp/translations/stories/standalone/xx-serieslanguage-index.txt (+189/-1) lib/lp/translations/templates/serieslanguage-index.pt (+39/-27) |
To merge this branch: | bzr merge lp:~adiroiban/launchpad/bug-427319 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Guilherme Salgado (community) | code | Approve | |
Martin Albisetti (community) | ui | Approve | |
Review via email: mp+15994@code.launchpad.net |
Commit message
Combine the ProductSeriesLa
Description of the change
Adi Roiban (adiroiban) wrote : | # |
Guilherme Salgado (salgado) wrote : | # |
Hi Adi,
This is a nice improvement to Launchpad's UI, but is also a very nice
refactoring -- I love when I see duplicated code being removed.
I think there's some room for us to improve the way the access-level
messages are generated, and I also have a few stylistic recommendations
below. Please let me know what you think.
review needsfixing
On Fri, 2009-12-11 at 01:30 +0000, Adi Roiban wrote:
> = Bug 427319 =
>
> The current note are a bit generic and also they refer to group
> coordinator, while linking to the group page (note to the group
> coordinator)
>
> == Proposed fix ==
>
> As a start add a direct link to team contact.
>
> If there is no team to manage the translations for a language, inform
> translators and refer the person that can be contacted for setting up
> a new group.
>
> Inform translators that they need to log in in order to make
> translations.
>
> Inform translators that their suggestion needs to be approved by the
> managing team and link to the team page.
>
> == Implementation details ==
>
> Since productseries-
> similar, I created a SeriesLangauge View inherited by both
> ProductSeries and DistroSeries.
>
> === removed file 'lib/lp/
> --- lib/lp/
> +++ lib/lp/
> @@ -1,57 +0,0 @@
> -# Copyright 2009 Canonical Ltd. This software is licensed under the
> -# GNU Affero General Public License version 3 (see the file LICENSE).
> -
> -"""Browser code for Product Series Languages."""
> -
> -__metaclass__ = type
> -
> -__all__ = [
> - 'ProductSeriesL
> - 'ProductSeriesL
> - ]
> -
> -from canonical.
> -from canonical.
> -from canonical.
> -from canonical.
> -from lp.translations
> - IProductSeriesL
> -
> -
> -class ProductSeriesLa
> - """View class to render translation status for an `IProductSeries
> -
> - pofiles = None
> - label = "Translatable templates"
> -
> - def initialize(self):
> - self.form = self.request.form
> -
> - self.batchnav = BatchNavigator(
> - self.context.
> - self.request)
> -
> - self.context.
> -
> - self.pofiles = self.context.
> - self.batchnav.
> - self.parent = self.context.
> -
> - @cachedproperty
> - def translation_
> - return self.context.
> -
> - @cachedproperty
> - def translation_
> - """Is there a translation team for this translation."""
> - if self.translatio
> - team = self.translatio
> - ...
Adi Roiban (adiroiban) wrote : | # |
Many thanks for the review.
Here is the diff.
=== modified file 'lib/lp/
--- lib/lp/
+++ lib/lp/
@@ -14,6 +14,7 @@
from canonical.
from canonical.
+from canonical.
from canonical.
from canonical.
from lp.translations
@@ -26,9 +27,12 @@
IProductSe
-class SeriesLanguageV
- """View class to render translation status for an `IDistroSeries`
- and `IProductSeries`"""
+class BaseSeriesLangu
+ """View base class to render translation status for an
+ `IDistroSeries` and `IProductSeries`
+
+ This class should not be directly instantiated.
+ """
pofiles = None
label = "Translatable templates"
@@ -36,7 +40,9 @@
parent = None
translatio
- def initialize(self):
+ def initialize(self, series, translationgroup):
+ self.series = series
+ self.translatio
self.form = self.request.form
@@ -46,14 +52,20 @@
- @cachedproperty
+ @property
def translation_
- """Is there a translation group for these translations."""
+ """Return the translation group for these translations.
+
+ Return None if there's no translation group for them.
+ """
return self.translatio
@cachedpro
def translation_
- """Is there a translation team for these translations."""
+ """Return the translation team for these translations.
+
+ Return None if there's no translation team for them.
+ """
if self.translatio
team = self.translatio
@@ -62,98 +74,80 @@
return team
@property
- def show_not_
- """Should we display a notice that user is not logged in?"""
- return self.user is None
+ def access_
+ if self.user is None:
+ return ("You are not logged in. Please log in to work " +
+ "on translations.")
- @property
- def show_no_
- """Should we display a notice that licence was not accepted?"""
- if self.show_
- return False
- return not translations_
-
- @property
- def show_full_
- """Should we display a notice that user is not logged in?"""
- if (self.show_
- self.show_
- return False
-
- ...
Guilherme Salgado (salgado) wrote : | # |
Hi Adi,
I have just a few minor suggestions but this will probably be the last
round; this branch should be ready to land once they're addressed.
> === renamed file 'lib/lp/
> --- lib/lp/
> +++ lib/lp/
>
> +
> +class BaseSeriesLangu
[...]
> @@ -47,7 +73,88 @@
> team = None
> return team
>
> + @property
> + def access_
> + if self.user is None:
> + return ("You are not logged in. Please log in to work " +
> + "on translations.")
When you use parenthesis for multi-line strings in python, they're
concatenated automatically, so you don't need the '+' here.
> +
> + translations_person = ITranslationsPe
> + translations_
> +
> + if self.translatio
> + translations_
> + self.translatio
> + elif self.translatio
> + translations_
> + self.translatio
Is it possible that self.translatio
both None, thus leaving translations_
I hope that's not possible as it'd cause the code below to crash, and in
that case we can make this assumption (that we expect at least one of
them to be non-None) clear in the code by adding an else block with an
AssertionError.
else:
raise AssertionError("No translation team/group found.")
And then you can also remove the "translations_
line.
> +
> + if not translations_
> + translation_
> + translations_
Have you tried calling .url(view_
think you need that because the URL generated by the code above is wrong
(e.g. ~user/translati
view_name argument it will make sure the generated URL is not a 404.
> + return ("To make translations in Launchpad you need to " +
> + "agree with the " +
No need to add the strings here either. :)
> + "<a href='%
> + translation_
> +
> + sample_pofile = self.pofiles[0]
> + if sample_pofile is not None:
> + if sample_
> + return "You can add and review translations."
> +
> + if sample_
> + return ("Your suggestions will be held for review by " +
> + "the managers of these translations. If you " +
> + "need help, or your translations are not being " +
> + "...
Martin Albisetti (beuno) wrote : | # |
11:50 < beuno> adiroiban, nice improvement
11:51 < beuno> a few questions
11:51 < beuno> why not "Your suggestions will be held for review by $TEAM"?
11:53 < beuno> there's also a missing full stop
11:53 < beuno> after the team's name
11:53 < beuno> before "Templates which are..."
11:53 < beuno> other than that, ui=me
11:54 < adiroiban> beuno: ok. I will change that.
Данило Шеган (danilo) wrote : | # |
Btw, nice refactoring Adi. Thanks for doing it! It'd be nice to move it even farther where we register only a single view (SeriesLanguage
Also, now that these views are unified, it makes sense to also unify the view tests: i.e. I believe mostly setUp method would have to be changed to accommodate different ways to construct distroserieslan
Adi Roiban (adiroiban) wrote : | # |
În data de Vi, 11-12-2009 la 17:48 +0000, Guilherme Salgado a scris:
> Hi Adi,
>
> I have just a few minor suggestions but this will probably be the last
> round; this branch should be ready to land once they're addressed.
>
> > === renamed file 'lib/lp/
> > --- lib/lp/
> > +++ lib/lp/
> >
> > +
> > +class BaseSeriesLangu
> [...]
> > @@ -47,7 +73,88 @@
> > team = None
> > return team
> >
> > + @property
> > + def access_
> > + if self.user is None:
> > + return ("You are not logged in. Please log in to work " +
> > + "on translations.")
>
> When you use parenthesis for multi-line strings in python, they're
> concatenated automatically, so you don't need the '+' here.
Done.
> > +
> > + translations_person = ITranslationsPe
> > + translations_
> > +
> > + if self.translatio
> > + translations_
> > + self.translatio
> > + elif self.translatio
> > + translations_
> > + self.translatio
>
> Is it possible that self.translatio
> both None, thus leaving translations_
> I hope that's not possible as it'd cause the code below to crash, and in
> that case we can make this assumption (that we expect at least one of
> them to be non-None) clear in the code by adding an else block with an
> AssertionError.
>
> else:
> raise AssertionError("No translation team/group found.")
>
> And then you can also remove the "translations_
> line.
It is possible and it should not be an error.
In such case no information should be display.
In a normal use case we should not reach this line, as that check is
already done in the template.
I added a check and returned "".
> > +
> > + if not translations_
> > + translation_
> > + translations_
>
> Have you tried calling .url(view_
> think you need that because the URL generated by the code above is wrong
> (e.g. ~user/translati
> view_name argument it will make sure the generated URL is not a 404.
>
> > + return ("To make translations in Launchpad you need to " +
> > + "agree with the " +
>
> No need to add the strings here either. :)
Done
> > + "<a href='%
> > + translation_
> > +
> > + sample_pofile = self.pofiles[0]
> > + if sample_pofile is not None:
> > + ...
Guilherme Salgado (salgado) wrote : | # |
Hi Adi,
Just a couple more suggestions, but these are really trivial. Once you
push the changes I'll submit your branch to ec2 to run the full test
suite.
Once again, thanks a lot for this improvement and the very nice cleanup!
review approve
status approved
On Fri, 2009-12-11 at 19:54 +0000, Adi Roiban wrote:
> În data de Vi, 11-12-2009 la 17:48 +0000, Guilherme Salgado a scris:
> > > +
> > > + translations_person = ITranslationsPe
> > > + translations_
> > > +
> > > + if self.translatio
> > > + translations_
> > > + self.translatio
> > > + elif self.translatio
> > > + translations_
> > > + self.translatio
> >
> > Is it possible that self.translatio
> > both None, thus leaving translations_
> > I hope that's not possible as it'd cause the code below to crash, and in
> > that case we can make this assumption (that we expect at least one of
> > them to be non-None) clear in the code by adding an else block with an
> > AssertionError.
> >
> > else:
> > raise AssertionError("No translation team/group found.")
> >
> > And then you can also remove the "translations_
> > line.
>
> It is possible and it should not be an error.
> In such case no information should be display.
>
> In a normal use case we should not reach this line, as that check is
> already done in the template.
>
> I added a check and returned "".
In this case I think the best is to document that in the method's
docstring (e.g. "This method must not be called if
self.translatio
with an assert, like this:
if self.translatio
...
else:
assert self.translatio
"Must not be called when there's no translation group.")
...
> > > +class DistroSeriesLan
> > > + """View class to render translation status for an `IDistroSeries`."""
> > > +
> > > + def initialize(self):
> > > + series = self.context.
> > > + super(DistroSer
> > > + series=series,
> > > + translationgrou
> > > + self.parent = self.series.
> > > +
> > > +
> > > +class ProductSeriesLa
> > > + """View class to render translation status for an `IProductSeries
> > > +
> > > + def initialize(self):
> > > + series = self.context.
> > > + super(ProductSe
> > > + series=series,
> >
> > I'd drop the 'series = self.context....' line in both classes above and
> > just pass series=
>
> I have leave that attribution to avoid a long line that will follow.
>
> translationgrou
Adi Roiban (adiroiban) wrote : | # |
În data de Vi, 11-12-2009 la 20:18 +0000, Guilherme Salgado a scris:
> Review: Approve
> Hi Adi,
>
> Just a couple more suggestions, but these are really trivial. Once you
> push the changes I'll submit your branch to ec2 to run the full test
> suite.
>
> Once again, thanks a lot for this improvement and the very nice cleanup!
>
> review approve
> status approved
[snip]
> In this case I think the best is to document that in the method's
> docstring (e.g. "This method must not be called if
> self.translatio
> with an assert, like this:
>
> if self.translatio
> ...
> else:
> assert self.translatio
> "Must not be called when there's no translation group.")
> ...
OK. Done.
> This check can go away once the if/elif is converted into an if/else
> with an assertion. The latter is preferred because it will fail
> horribly (instead of silently, like it currently does) when the property
> is used incorrectly (i.e. when translation_group is None).
Yep.
> > +
> > if not translations_
> > translation_
> > - translations_
> > - return ("To make translations in Launchpad you need to " +
> > - "agree with the " +
> > + translations_
> > + view_name=
> > + rootsite=
>
> You might not need rootsite=
> the translations rootsite), but I might be wrong.
I've checked the implementation for PersonFormatter
I can understand that code, the rootsite is not automatically detected.
rootsite is required... as +licensing is not valid for main.
Also tested without rootsite and it was not created for translations.
> > +Users will see three references to the team managing these translations.
> > +
> > + >>> print user_browser.
> > + ... 'Ubuntu Spanish Translator'
> > + http://
> > +
> > + >>> print user_browser.
> > + ... 'Ubuntu Spanish Translator'
> > + http://
> > +
> > + >>> print user_browser.
> > + ... 'Ubuntu Spanish Translator'
> > + http://
>
> Is it really important to show that we have 3 links to the team managing
> the translations?
Fixed.
I added new tests for checking when a translator has not agreed the
translation license and when translations are CLOSED.
Merged with devel since I modified the Factory in a branch that was just
commited and those changes also affects these tests.
Cheers.
Here is the diff.
=== modified file 'lib/lp/
--- lib/lp/
+0000
+++ lib/lp/
+0000
@@ -75,6 +75,8 @@
@property
def access_
+...
Guilherme Salgado (salgado) : | # |
Adi Roiban (adiroiban) wrote : | # |
Hi Salgado,
The failing test is /home/adi/
def test_translatio
group = self.factory.
I tried to change it to the following code... but with no luck
def test_translatio
group = self.factory.
self.view = DistroSeriesLan
-------
This is a fix for the view, but maybe I have to fix the test.
=== modified file 'lib/lp/
--- lib/lp/
+++ lib/lp/
@@ -38,11 +38,9 @@
label = "Translatable templates"
series = None
parent = None
- translationgroup = None
def initialize(self, series, translationgroup):
- self.translatio
self.form = self.request.form
@@ -58,7 +56,10 @@
Return None if there's no translation group for them.
"""
- return self.translatio
+ if (self.context.
+ return self.context.
+ else:
+ return self.context.
@cachedpro
def translation_
@@ -134,25 +135,21 @@
-class DistroSeriesLan
+class DistroSeriesLan
"""View class to render translation status for an `IDistroSeries`."""
def initialize(self):
- series = self.context.
- series=series,
- translationgrou
+ series=
-class ProductSeriesLa
+class ProductSeriesLa
"""View class to render translation status for an `IProductSeries
def initialize(self):
- series = self.context.
- series=series,
- translationgrou
+ series=
Adi Roiban (adiroiban) wrote : | # |
I start a full test.
Here is the diff for the tests.
=== modified file 'lib/lp/
--- lib/lp/
+++ lib/lp/
@@ -48,6 +48,8 @@
group = self.factory.
+ self.view = DistroSeriesLan
+ self.dsl, LaunchpadTestRe
def test_translatio
@@ -65,7 +67,7 @@
group, self.language, team)
# Recreate the view because we are using a cached property.
self.view = DistroSeriesLan
- self.dsl, LaunchpadTestRe
+ self.dsl, LaunchpadTestRe
def test_suite():
=== modified file 'lib/lp/
--- lib/lp/
+++ lib/lp/
@@ -137,6 +137,8 @@
group = self.factory.
+ self.view = ProductSeriesLa
+ self.psl, LaunchpadTestRe
def test_translatio
@@ -154,7 +156,7 @@
group, self.language, team)
# Recreate the view because we are using a cached property.
self.view = ProductSeriesLa
- self.psl, LaunchpadTestRe
+ self.psl, LaunchpadTestRe
def test_suite():
Adi Roiban (adiroiban) wrote : | # |
I did a full test using the latest branch and everything is OK now. We will see the truth on Monday :)
Preview Diff
1 | === modified file 'lib/lp/translations/browser/configure.zcml' |
2 | --- lib/lp/translations/browser/configure.zcml 2009-12-12 02:14:16 +0000 |
3 | +++ lib/lp/translations/browser/configure.zcml 2009-12-17 16:29:14 +0000 |
4 | @@ -161,9 +161,8 @@ |
5 | attribute_to_parent="distroseries" |
6 | rootsite="translations"/> |
7 | <browser:navigation |
8 | - module="lp.translations.browser.distroserieslanguage" |
9 | - classes=" |
10 | - DistroSeriesLanguageNavigation"/> |
11 | + module="lp.translations.browser.serieslanguage" |
12 | + classes="DistroSeriesLanguageNavigation"/> |
13 | <browser:defaultView |
14 | for="lp.translations.interfaces.distroserieslanguage.IDistroSeriesLanguage" |
15 | name="+index" |
16 | @@ -182,7 +181,7 @@ |
17 | permission="zope.Public" |
18 | for="lp.translations.interfaces.distroserieslanguage.IDistroSeriesLanguage" |
19 | template="../templates/serieslanguage-index.pt" |
20 | - class="lp.translations.browser.distroserieslanguage.DistroSeriesLanguageView" |
21 | + class="lp.translations.browser.serieslanguage.DistroSeriesLanguageView" |
22 | facet="translations" |
23 | layer="canonical.launchpad.layers.TranslationsLayer"/> |
24 | <facet |
25 | @@ -272,7 +271,7 @@ |
26 | attribute_to_parent="productseries" |
27 | rootsite="translations"/> |
28 | <browser:navigation |
29 | - module="lp.translations.browser.productserieslanguage" |
30 | + module="lp.translations.browser.serieslanguage" |
31 | classes=" |
32 | ProductSeriesLanguageNavigation"/> |
33 | <browser:defaultView |
34 | @@ -292,7 +291,7 @@ |
35 | permission="zope.Public" |
36 | for="lp.translations.interfaces.productserieslanguage.IProductSeriesLanguage" |
37 | template="../templates/serieslanguage-index.pt" |
38 | - class="lp.translations.browser.productserieslanguage.ProductSeriesLanguageView" |
39 | + class="lp.translations.browser.serieslanguage.ProductSeriesLanguageView" |
40 | facet="translations" |
41 | layer="canonical.launchpad.layers.TranslationsLayer"/> |
42 | <facet |
43 | |
44 | === removed file 'lib/lp/translations/browser/productserieslanguage.py' |
45 | --- lib/lp/translations/browser/productserieslanguage.py 2009-09-17 12:45:52 +0000 |
46 | +++ lib/lp/translations/browser/productserieslanguage.py 1970-01-01 00:00:00 +0000 |
47 | @@ -1,57 +0,0 @@ |
48 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
49 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
50 | - |
51 | -"""Browser code for Product Series Languages.""" |
52 | - |
53 | -__metaclass__ = type |
54 | - |
55 | -__all__ = [ |
56 | - 'ProductSeriesLanguageNavigation', |
57 | - 'ProductSeriesLanguageView', |
58 | - ] |
59 | - |
60 | -from canonical.cachedproperty import cachedproperty |
61 | -from canonical.launchpad.webapp import LaunchpadView |
62 | -from canonical.launchpad.webapp.batching import BatchNavigator |
63 | -from canonical.launchpad.webapp.publisher import Navigation |
64 | -from lp.translations.interfaces.productserieslanguage import ( |
65 | - IProductSeriesLanguage) |
66 | - |
67 | - |
68 | -class ProductSeriesLanguageView(LaunchpadView): |
69 | - """View class to render translation status for an `IProductSeries`.""" |
70 | - |
71 | - pofiles = None |
72 | - label = "Translatable templates" |
73 | - |
74 | - def initialize(self): |
75 | - self.form = self.request.form |
76 | - |
77 | - self.batchnav = BatchNavigator( |
78 | - self.context.productseries.getCurrentTranslationTemplates(), |
79 | - self.request) |
80 | - |
81 | - self.context.recalculateCounts() |
82 | - |
83 | - self.pofiles = self.context.getPOFilesFor( |
84 | - self.batchnav.currentBatch()) |
85 | - self.parent = self.context.productseries.product |
86 | - |
87 | - @cachedproperty |
88 | - def translation_group(self): |
89 | - return self.context.productseries.product.translationgroup |
90 | - |
91 | - @cachedproperty |
92 | - def translation_team(self): |
93 | - """Is there a translation team for this translation.""" |
94 | - if self.translation_group is not None: |
95 | - team = self.translation_group.query_translator( |
96 | - self.context.language) |
97 | - else: |
98 | - team = None |
99 | - return team |
100 | - |
101 | - |
102 | -class ProductSeriesLanguageNavigation(Navigation): |
103 | - """Navigation for `IProductSeriesLanguage`.""" |
104 | - usedfor = IProductSeriesLanguage |
105 | |
106 | === renamed file 'lib/lp/translations/browser/distroserieslanguage.py' => 'lib/lp/translations/browser/serieslanguage.py' |
107 | --- lib/lp/translations/browser/distroserieslanguage.py 2009-09-17 11:10:49 +0000 |
108 | +++ lib/lp/translations/browser/serieslanguage.py 2009-12-17 16:29:14 +0000 |
109 | @@ -8,38 +8,64 @@ |
110 | __all__ = [ |
111 | 'DistroSeriesLanguageNavigation', |
112 | 'DistroSeriesLanguageView', |
113 | + 'ProductSeriesLanguageNavigation', |
114 | + 'ProductSeriesLanguageView', |
115 | ] |
116 | |
117 | +from canonical.cachedproperty import cachedproperty |
118 | from canonical.launchpad.webapp import LaunchpadView |
119 | +from canonical.launchpad.webapp.tales import PersonFormatterAPI |
120 | from canonical.launchpad.webapp.batching import BatchNavigator |
121 | from canonical.launchpad.webapp.publisher import Navigation |
122 | from lp.translations.interfaces.distroserieslanguage import ( |
123 | IDistroSeriesLanguage) |
124 | - |
125 | -class DistroSeriesLanguageView(LaunchpadView): |
126 | - """View class to render translation status for an `IDistroSeries`.""" |
127 | +from lp.translations.interfaces.translationsperson import ( |
128 | + ITranslationsPerson) |
129 | +from lp.translations.interfaces.translationgroup import ( |
130 | + TranslationPermission) |
131 | +from lp.translations.interfaces.productserieslanguage import ( |
132 | + IProductSeriesLanguage) |
133 | + |
134 | + |
135 | +class BaseSeriesLanguageView(LaunchpadView): |
136 | + """View base class to render translation status for an |
137 | + `IDistroSeries` and `IProductSeries` |
138 | + |
139 | + This class should not be directly instantiated. |
140 | + """ |
141 | |
142 | pofiles = None |
143 | label = "Translatable templates" |
144 | + series = None |
145 | + parent = None |
146 | + translationgroup = None |
147 | |
148 | - def initialize(self): |
149 | + def initialize(self, series, translationgroup): |
150 | + self.series = series |
151 | + self.translationgroup = translationgroup |
152 | self.form = self.request.form |
153 | |
154 | self.batchnav = BatchNavigator( |
155 | - self.context.distroseries.getCurrentTranslationTemplates(), |
156 | + self.series.getCurrentTranslationTemplates(), |
157 | self.request) |
158 | |
159 | self.pofiles = self.context.getPOFilesFor( |
160 | self.batchnav.currentBatch()) |
161 | - self.parent = self.context.distroseries.distribution |
162 | |
163 | @property |
164 | def translation_group(self): |
165 | - return self.context.distroseries.distribution.translationgroup |
166 | - |
167 | - @property |
168 | + """Return the translation group for these translations. |
169 | + |
170 | + Return None if there's no translation group for them. |
171 | + """ |
172 | + return self.translationgroup |
173 | + |
174 | + @cachedproperty |
175 | def translation_team(self): |
176 | - """Is there a translation team for this translation.""" |
177 | + """Return the translation team for these translations. |
178 | + |
179 | + Return None if there's no translation team for them. |
180 | + """ |
181 | if self.translation_group is not None: |
182 | team = self.translation_group.query_translator( |
183 | self.context.language) |
184 | @@ -47,7 +73,95 @@ |
185 | team = None |
186 | return team |
187 | |
188 | + @property |
189 | + def access_level_description(self): |
190 | + """Must not be called when there's no translation group.""" |
191 | + |
192 | + if self.user is None: |
193 | + return ("You are not logged in. Please log in to work " |
194 | + "on translations.") |
195 | + |
196 | + translations_person = ITranslationsPerson(self.user) |
197 | + translations_contact_link = None |
198 | + |
199 | + if self.translation_team: |
200 | + translations_contact_link = PersonFormatterAPI( |
201 | + self.translation_team.translator).link(None) |
202 | + elif self.translation_group: |
203 | + translations_contact_link = PersonFormatterAPI( |
204 | + self.translation_group.owner).link(None) |
205 | + else: |
206 | + assert self.translation_group is not None, ( |
207 | + "Must not be called when there's no translation group.") |
208 | + |
209 | + if not translations_person.translations_relicensing_agreement: |
210 | + translation_license_url = PersonFormatterAPI( |
211 | + self.user).url( |
212 | + view_name='+licensing', |
213 | + rootsite='translations') |
214 | + return ("To make translations in Launchpad you need to " |
215 | + "agree with the " |
216 | + "<a href='%s'>Translations licensing</a>.") % ( |
217 | + translation_license_url) |
218 | + |
219 | + sample_pofile = self.pofiles[0] |
220 | + if sample_pofile is not None: |
221 | + if sample_pofile.canEditTranslations(self.user): |
222 | + return "You can add and review translations." |
223 | + |
224 | + if sample_pofile.canAddSuggestions(self.user): |
225 | + return ("Your suggestions will be held for review by " |
226 | + "%s. If you need help, or your translations are " |
227 | + "not being reviewed, please get in touch with " |
228 | + "%s.") % ( |
229 | + translations_contact_link, |
230 | + translations_contact_link) |
231 | + |
232 | + permission = sample_pofile.translationpermission |
233 | + if permission == TranslationPermission.CLOSED: |
234 | + return ("These templates can be translated only by " |
235 | + "their managers.") |
236 | + |
237 | + if self.translation_team is None: |
238 | + return ("Since there is nobody to manage translation " |
239 | + "approvals into this language, your cannot add " |
240 | + "new suggestions. If you are interested in making " |
241 | + "translations, please contact %s.") % ( |
242 | + translations_contact_link) |
243 | + |
244 | + raise AssertionError( |
245 | + "BUG! Couldn't identify the user's access level for these " |
246 | + "translations.") |
247 | + |
248 | + |
249 | +class DistroSeriesLanguageView(BaseSeriesLanguageView): |
250 | + """View class to render translation status for an `IDistroSeries`.""" |
251 | + |
252 | + def initialize(self): |
253 | + series = self.context.distroseries |
254 | + super(DistroSeriesLanguageView, self).initialize( |
255 | + series=series, |
256 | + translationgroup=series.distribution.translationgroup) |
257 | + self.parent = self.series.distribution |
258 | + |
259 | + |
260 | +class ProductSeriesLanguageView(BaseSeriesLanguageView): |
261 | + """View class to render translation status for an `IProductSeries`.""" |
262 | + |
263 | + def initialize(self): |
264 | + series = self.context.productseries |
265 | + super(ProductSeriesLanguageView, self).initialize( |
266 | + series=series, |
267 | + translationgroup=series.product.translationgroup) |
268 | + self.context.recalculateCounts() |
269 | + self.parent = self.series.product |
270 | + |
271 | |
272 | class DistroSeriesLanguageNavigation(Navigation): |
273 | """Navigation for `IDistroSeriesLanguage`.""" |
274 | usedfor = IDistroSeriesLanguage |
275 | + |
276 | + |
277 | +class ProductSeriesLanguageNavigation(Navigation): |
278 | + """Navigation for `IProductSeriesLanguage`.""" |
279 | + usedfor = IProductSeriesLanguage |
280 | |
281 | === modified file 'lib/lp/translations/browser/tests/test_distroserieslanguage_views.py' |
282 | --- lib/lp/translations/browser/tests/test_distroserieslanguage_views.py 2009-09-11 06:57:21 +0000 |
283 | +++ lib/lp/translations/browser/tests/test_distroserieslanguage_views.py 2009-12-17 16:29:14 +0000 |
284 | @@ -8,7 +8,7 @@ |
285 | |
286 | from zope.component import getUtility |
287 | |
288 | -from lp.translations.browser.distroserieslanguage import ( |
289 | +from lp.translations.browser.serieslanguage import ( |
290 | DistroSeriesLanguageView) |
291 | from lp.translations.interfaces.translator import ITranslatorSet |
292 | from canonical.launchpad.webapp.servers import LaunchpadTestRequest |
293 | @@ -48,6 +48,9 @@ |
294 | group = self.factory.makeTranslationGroup( |
295 | self.distroseries.distribution.owner, url=None) |
296 | self.distroseries.distribution.translationgroup = group |
297 | + self.view = DistroSeriesLanguageView( |
298 | + self.dsl, LaunchpadTestRequest()) |
299 | + self.view.initialize() |
300 | self.assertEquals(self.view.translation_group, group) |
301 | |
302 | def test_translation_team(self): |
303 | @@ -66,6 +69,7 @@ |
304 | # Recreate the view because we are using a cached property. |
305 | self.view = DistroSeriesLanguageView( |
306 | self.dsl, LaunchpadTestRequest()) |
307 | + self.view.initialize() |
308 | self.assertEquals(self.view.translation_team, translator) |
309 | |
310 | def test_suite(): |
311 | |
312 | === modified file 'lib/lp/translations/browser/tests/test_productserieslanguage_views.py' |
313 | --- lib/lp/translations/browser/tests/test_productserieslanguage_views.py 2009-07-17 00:26:05 +0000 |
314 | +++ lib/lp/translations/browser/tests/test_productserieslanguage_views.py 2009-12-17 16:29:14 +0000 |
315 | @@ -7,7 +7,7 @@ |
316 | |
317 | from zope.component import getUtility |
318 | |
319 | -from lp.translations.browser.productserieslanguage import ( |
320 | +from lp.translations.browser.serieslanguage import ( |
321 | ProductSeriesLanguageView) |
322 | from lp.translations.interfaces.translator import ITranslatorSet |
323 | from canonical.launchpad.webapp.servers import LaunchpadTestRequest |
324 | @@ -137,6 +137,7 @@ |
325 | group = self.factory.makeTranslationGroup( |
326 | self.productseries.product.owner, url=None) |
327 | self.productseries.product.translationgroup = group |
328 | + self.view.initialize() |
329 | self.assertEquals(self.view.translation_group, group) |
330 | |
331 | def test_translation_team(self): |
332 | @@ -155,6 +156,7 @@ |
333 | # Recreate the view because we are using a cached property. |
334 | self.view = ProductSeriesLanguageView( |
335 | self.psl, LaunchpadTestRequest()) |
336 | + self.view.initialize() |
337 | self.assertEquals(self.view.translation_team, translator) |
338 | |
339 | def test_suite(): |
340 | |
341 | === renamed file 'lib/lp/translations/stories/productseries/xx-productserieslanguage.txt' => 'lib/lp/translations/stories/standalone/xx-serieslanguage-index.txt' |
342 | --- lib/lp/translations/stories/productseries/xx-productserieslanguage.txt 2009-09-12 07:25:21 +0000 |
343 | +++ lib/lp/translations/stories/standalone/xx-serieslanguage-index.txt 2009-12-17 16:29:14 +0000 |
344 | @@ -1,4 +1,9 @@ |
345 | -= Product release series language overview = |
346 | +Product or distribution release series language overview |
347 | +======================================================== |
348 | + |
349 | +These pages are used by translators for accessing all templates in a |
350 | +release series and viewing translation statistics for each template for |
351 | +a specific language |
352 | |
353 | The Translations page for a product release series with multiple |
354 | templates links to per-language overviews. |
355 | @@ -11,4 +16,187 @@ |
356 | >>> print browser.title |
357 | Portuguese (Brazil) (pt_BR) : Translations : Series trunk : Evolution |
358 | |
359 | +Since there is no translation team to manage Portuguese (Brazil) language |
360 | +in the Evolution's translation group, all users will be informed about it |
361 | +and pointed to the translation group owner. |
362 | + |
363 | + >>> print extract_text(find_tag_by_id( |
364 | + ... browser.contents, 'group-team-info')) |
365 | + There is no team to manage Evolution ... translations to ... |
366 | + To set one up, please get in touch with Carlos Perelló Marín. |
367 | + |
368 | +Anonymous users are informed that in order to make translations they |
369 | +need to login first. |
370 | + |
371 | + >>> print extract_text( |
372 | + ... find_tag_by_id(browser.contents, 'translation-access-level')) |
373 | + You are not logged in. Please log in to work on translations... |
374 | + |
375 | +Authenticated users will see information about what they can do in |
376 | +these translations. Things like review, only add suggestion or no |
377 | +changes at all. |
378 | + |
379 | +If a product or distribution had no translation group, visitors are |
380 | +informed about this fact and will be able to add translations without |
381 | +requiring a review. |
382 | + |
383 | + >>> user_browser.open( |
384 | + ... 'http://translations.launchpad.dev/ubuntu/hoary/+lang/es') |
385 | + >>> print extract_text( |
386 | + ... find_tag_by_id(user_browser.contents, 'group-team-info')) |
387 | + There is no translation group to manage Ubuntu translations. |
388 | + |
389 | +Create a translation group for Ubuntu, together with a translation |
390 | +person for managing Ubuntu Spanish translations and set translation |
391 | +policy to RESTRICTED. |
392 | +This is done to so see what the page will look like when they exist. |
393 | + |
394 | + >>> from zope.component import getUtility |
395 | + >>> from canonical.launchpad.interfaces import ILaunchpadCelebrities |
396 | + >>> from lp.translations.interfaces.translationgroup import ( |
397 | + ... TranslationPermission) |
398 | + >>> login('foo.bar@canonical.com') |
399 | + >>> utc_owner = factory.makePerson(displayname='Some Guy') |
400 | + >>> utc_team = factory.makeTeam( |
401 | + ... owner=utc_owner, name='utc-team', |
402 | + ... displayname='Ubuntu Translation Coordinators') |
403 | + >>> utg = factory.makeTranslationGroup( |
404 | + ... owner=utc_team, name='utg', title='Ubuntu Translation Group') |
405 | + >>> st_coordinator = factory.makePerson( |
406 | + ... name="ubuntu-l10n-es", |
407 | + ... displayname='Ubuntu Spanish Translators') |
408 | + >>> dude = factory.makePerson( |
409 | + ... name="dude", email="dude@ex.com", password="test") |
410 | + >>> ubuntu = getUtility(ILaunchpadCelebrities).ubuntu |
411 | + >>> ubuntu.translationgroup = utg |
412 | + >>> ubuntu.translationpermission = TranslationPermission.RESTRICTED |
413 | + >>> translators = factory.makeTranslator( |
414 | + ... 'es', group=utg, person=st_coordinator) |
415 | + >>> no_license_translator = factory.makeTranslator( |
416 | + ... 'es', person=dude, license=False) |
417 | + >>> logout() |
418 | + |
419 | +Spanish has a translation team for managing its translations and all |
420 | +Evolution Spanish templates can be accessed from the distribution series |
421 | +translation page. |
422 | + |
423 | + >>> user_browser.open( |
424 | + ... 'http://translations.launchpad.dev/ubuntu/hoary/') |
425 | + >>> user_browser.getLink('Spanish').click() |
426 | + >>> print user_browser.url |
427 | + http://translations.launchpad.dev/ubuntu/hoary/+lang/es |
428 | + |
429 | + >>> print extract_text( |
430 | + ... find_tag_by_id(user_browser.contents, 'group-team-info')) |
431 | + These Ubuntu translations are managed by Ubuntu Spanish Translators. |
432 | + |
433 | +Authenticated users can add suggestion but will be held for review by |
434 | +the members of Spanish translations team. |
435 | + |
436 | + >>> print extract_text( |
437 | + ... find_tag_by_id( |
438 | + ... user_browser.contents, 'translation-access-level')) |
439 | + Your suggestions will be held for review by |
440 | + Ubuntu Spanish Translator... |
441 | + please get in touch with Ubuntu Spanish Translators... |
442 | + |
443 | +Users will see three references to the team managing these translations. |
444 | + |
445 | + >>> print user_browser.getLink( |
446 | + ... 'Ubuntu Spanish Translator').url |
447 | + http://launchpad.dev/~ubuntu-l10n-es |
448 | + |
449 | +Catalan has no translation team for managing translations and since |
450 | +there is no one to review the work, authenticated users can not add |
451 | +suggestions. |
452 | + |
453 | + >>> user_browser.open( |
454 | + ... 'http://translations.launchpad.dev/ubuntu/hoary/') |
455 | + >>> user_browser.getLink('Catalan').click() |
456 | + >>> print user_browser.url |
457 | + http://translations.launchpad.dev/ubuntu/hoary/+lang/ca |
458 | + |
459 | + >>> print extract_text( |
460 | + ... find_tag_by_id(user_browser.contents, 'group-team-info')) |
461 | + There is no team to manage ... To set one up, please get in touch |
462 | + with Ubuntu Translation Coordinators. |
463 | + |
464 | + >>> print extract_text(find_tag_by_id( |
465 | + ... user_browser.contents, 'translation-access-level')) |
466 | + Since there is nobody to manage translation ... |
467 | + your cannot add new suggestions. If you are interested in making |
468 | + translations, please contact Ubuntu Translation Coordinators... |
469 | + |
470 | + >>> print user_browser.getLink( |
471 | + ... 'Ubuntu Translation Coordinators').url |
472 | + http://launchpad.dev/~utc-team |
473 | + |
474 | +Members of translation team and translations admins have full access to |
475 | +translations. They can add and review translations. |
476 | + |
477 | + >>> admin_browser.open( |
478 | + ... 'http://translations.launchpad.dev/ubuntu/hoary/+lang/ro') |
479 | + >>> print extract_text(find_tag_by_id( |
480 | + ... admin_browser.contents, 'translation-access-level')) |
481 | + You can add and review translations... |
482 | + |
483 | +For projects using closed translations policy, a translator that is not |
484 | +member of the translation team appointed for that language will not |
485 | +be allowed to make any changes. |
486 | + |
487 | + >>> login('foo.bar@canonical.com') |
488 | + >>> ubuntu.translationpermission = TranslationPermission.CLOSED |
489 | + >>> logout() |
490 | + |
491 | + >>> user_browser.open( |
492 | + ... 'http://translations.launchpad.dev/ubuntu/hoary/+lang/ro') |
493 | + >>> print extract_text(find_tag_by_id( |
494 | + ... user_browser.contents, 'translation-access-level')) |
495 | + These templates can be translated only by their managers... |
496 | + |
497 | +Translation policy is rolled back to not affect other tests. |
498 | + |
499 | + >>> login('foo.bar@canonical.com') |
500 | + >>> ubuntu.translationpermission = TranslationPermission.RESTRICTED |
501 | + >>> logout() |
502 | + |
503 | +Translators that have not agreed with the license can not make |
504 | +translations, and will see a link to the license page. |
505 | + |
506 | + >>> no_license_browser = setupBrowser( |
507 | + ... auth='Basic dude@ex.com:test') |
508 | + >>> no_license_browser.open( |
509 | + ... 'http://translations.launchpad.dev/ubuntu/hoary/+lang/ro') |
510 | + >>> print extract_text(find_tag_by_id( |
511 | + ... no_license_browser.contents, 'translation-access-level')) |
512 | + To make translations in Launchpad you need to agree with |
513 | + the Translations licensing... |
514 | + |
515 | + >>> print no_license_browser.getLink( |
516 | + ... 'Translations licensing').url |
517 | + http://translations.launchpad.dev/~dude/+licensing |
518 | + |
519 | +For project with no translation group, translators see a note stating |
520 | +this fact. No access level information is displayed. |
521 | + |
522 | + >>> login('foo.bar@canonical.com') |
523 | + >>> ubuntu.translationgroup = None |
524 | + >>> logout() |
525 | + |
526 | + >>> user_browser.open( |
527 | + ... 'http://translations.launchpad.dev/ubuntu/hoary/+lang/ro') |
528 | + >>> print extract_text( |
529 | + ... find_tag_by_id(user_browser.contents, 'group-team-info')) |
530 | + There is no translation group to manage Ubuntu translations. |
531 | + |
532 | + >>> print extract_text(find_tag_by_id( |
533 | + ... user_browser.contents, 'translation-access-level')) |
534 | + Templates which are more important to translate are listed first. |
535 | + |
536 | +Translation group configuration is rolled back to not affect other tests. |
537 | + |
538 | + >>> login('foo.bar@canonical.com') |
539 | + >>> ubuntu.translationgroup = utg |
540 | + >>> logout() |
541 | + |
542 | The details of the page are tested at the view level. |
543 | |
544 | === modified file 'lib/lp/translations/templates/serieslanguage-index.pt' |
545 | --- lib/lp/translations/templates/serieslanguage-index.pt 2009-09-17 11:10:49 +0000 |
546 | +++ lib/lp/translations/templates/serieslanguage-index.pt 2009-12-17 16:29:14 +0000 |
547 | @@ -16,33 +16,45 @@ |
548 | |
549 | <tal:block condition="view/pofiles"> |
550 | <div class="top-portlet"> |
551 | - <tal:with_group condition="view/translation_group"> |
552 | - <p tal:condition="view/translation_team"> |
553 | - <tal:product replace="structure |
554 | - view/parent/fmt:link">Evolution</tal:product> |
555 | - is translated by |
556 | - <tal:team replace="structure |
557 | - view/translation_team/translator/fmt:link"> |
558 | - Serbian translators</tal:team> — if you need help, or your |
559 | - translations are not being reviewed, please get in touch with them. |
560 | - </p> |
561 | - |
562 | - <p tal:condition="not:view/translation_team"> |
563 | - There is no team to translate |
564 | - <tal:product replace="structure |
565 | - view/parent/fmt:link">Evolution</tal:product> |
566 | - to |
567 | - <a tal:attributes="href context/language/fmt:url" |
568 | - tal:content="context/language/englishname">Serbian</a>. |
569 | - To set one up, please get in touch with |
570 | - <a tal:replace="structure view/translation_group/fmt:link" |
571 | - >Launchpad Translators</a> |
572 | - coordinator. |
573 | - </p> |
574 | - </tal:with_group> |
575 | - |
576 | - <p> |
577 | - Choose a template name to begin translating. |
578 | + <div id="group-team-info"> |
579 | + <tal:with_group condition="view/translation_group"> |
580 | + <p tal:condition="view/translation_team"> |
581 | + These |
582 | + <tal:product replace="structure |
583 | + view/parent/fmt:link">Evolution</tal:product> |
584 | + translations are managed by |
585 | + <tal:team replace="structure |
586 | + view/translation_team/translator/fmt:link"> |
587 | + Serbian translators</tal:team>. |
588 | + </p> |
589 | + |
590 | + <p tal:condition="not:view/translation_team"> |
591 | + There is no team to manage |
592 | + <tal:product replace="structure |
593 | + view/parent/fmt:link">Evolution</tal:product> |
594 | + translations to |
595 | + <a tal:attributes="href context/language/fmt:url" |
596 | + tal:content="context/language/englishname">Serbian</a>. |
597 | + To set one up, please get in touch with |
598 | + <a tal:replace="structure view/translation_group/owner/fmt:link" |
599 | + >Launchpad Translators</a>. |
600 | + </p> |
601 | + </tal:with_group> |
602 | + <tal:without_group condition="not: view/translation_group"> |
603 | + <p> |
604 | + There is no translation group to manage |
605 | + <tal:product replace="structure |
606 | + view/parent/fmt:link">Evolution</tal:product> |
607 | + translations. |
608 | + </p> |
609 | + </tal:without_group> |
610 | + </div> |
611 | + <p id="translation-access-level"> |
612 | + <span |
613 | + tal:condition="view/translation_group" |
614 | + tal:replace="structure view/access_level_description"> |
615 | + You can add suggestions for these translations. |
616 | + </span> |
617 | Templates which are more important to translate are listed first. |
618 | </p> |
619 | </div> |
= Bug 427319 =
The current note are a bit generic and also they refer to group coordinator, while linking to the group page (note to the group coordinator)
== Proposed fix ==
As a start add a direct link to team contact.
If there is no team to manage the translations for a language, inform translators and refer the person that can be contacted for setting up a new group.
Inform translators that they need to log in in order to make translations.
Inform translators that their suggestion needs to be approved by the managing team and link to the team page.
== Implementation details ==
Since productseries- langauge and distroseries- language views were very similar, I created a SeriesLangauge View inherited by both ProductSeries and DistroSeries.
== Tests ==
New tests were added to cover the added functionality.
./bin/test -vvc -m translations --layer PageTestLayer
== Demo and Q/A == /translations. launchpad. dev/ubuntu/ +settings
Log in as an Admin and change Ubuntu settings so that there is no translation group for Ubuntu
https:/
Then go to these pages with an anonymous, an authententicated and admin users.
Observe how the note on top of the templates table changes to reflect you current access to the translations.
http:// translations. launchpad. dev/ubuntu/ hoary/+ lang/es translations. launchpad. dev/ubuntu/ hoary/+ lang/pt_ BR
http://
There is a detailed testplan in standalone/ xx-serieslangua ge-index. pt
= Launchpad lint =
Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.
Linting changed files: translations/ browser/ serieslanguage. py translations/ templates/ serieslanguage- index.pt
lib/lp/
lib/lp/