Merge lp:~adiroiban/launchpad/bug-427319 into lp:launchpad

Proposed by Adi Roiban
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
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 ProductSeriesLanguage and DistroSeriesLanguage in a common view. Inform translators about what they can do for that series translations.

To post a comment you must log in.
Revision history for this message
Adi Roiban (adiroiban) 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-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 ==
Log in as an Admin and change Ubuntu settings so that there is no translation group for Ubuntu
https://translations.launchpad.dev/ubuntu/+settings

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
http://translations.launchpad.dev/ubuntu/hoary/+lang/pt_BR

There is a detailed testplan in standalone/xx-serieslanguage-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:
  lib/lp/translations/browser/serieslanguage.py
  lib/lp/translations/templates/serieslanguage-index.pt

Revision history for this message
Guilherme Salgado (salgado) wrote :
Download full text (19.2 KiB)

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-langauge and distroseries-language views were very
> similar, I created a SeriesLangauge View inherited by both
> ProductSeries and DistroSeries.
>

> === removed file 'lib/lp/translations/browser/productserieslanguage.py'
> --- lib/lp/translations/browser/productserieslanguage.py 2009-09-17 12:45:52 +0000
> +++ lib/lp/translations/browser/productserieslanguage.py 1970-01-01 00:00:00 +0000
> @@ -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__ = [
> - 'ProductSeriesLanguageNavigation',
> - 'ProductSeriesLanguageView',
> - ]
> -
> -from canonical.cachedproperty import cachedproperty
> -from canonical.launchpad.webapp import LaunchpadView
> -from canonical.launchpad.webapp.batching import BatchNavigator
> -from canonical.launchpad.webapp.publisher import Navigation
> -from lp.translations.interfaces.productserieslanguage import (
> - IProductSeriesLanguage)
> -
> -
> -class ProductSeriesLanguageView(LaunchpadView):
> - """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.productseries.getCurrentTranslationTemplates(),
> - self.request)
> -
> - self.context.recalculateCounts()
> -
> - self.pofiles = self.context.getPOFilesFor(
> - self.batchnav.currentBatch())
> - self.parent = self.context.productseries.product
> -
> - @cachedproperty
> - def translation_group(self):
> - return self.context.productseries.product.translationgroup
> -
> - @cachedproperty
> - def translation_team(self):
> - """Is there a translation team for this translation."""
> - if self.translation_group is not None:
> - team = self.translation_group.query_translator(
> - ...

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

Many thanks for the review.
Here is the diff.

=== modified file 'lib/lp/translations/browser/serieslanguage.py'
--- lib/lp/translations/browser/serieslanguage.py 2009-12-11 01:12:22 +0000
+++ lib/lp/translations/browser/serieslanguage.py 2009-12-11 16:09:04 +0000
@@ -14,6 +14,7 @@

 from canonical.cachedproperty import cachedproperty
 from canonical.launchpad.webapp import LaunchpadView
+from canonical.launchpad.webapp.tales import PersonFormatterAPI
 from canonical.launchpad.webapp.batching import BatchNavigator
 from canonical.launchpad.webapp.publisher import Navigation
 from lp.translations.interfaces.distroserieslanguage import (
@@ -26,9 +27,12 @@
     IProductSeriesLanguage)

-class SeriesLanguageView(LaunchpadView):
- """View class to render translation status for an `IDistroSeries`
- and `IProductSeries`"""
+class BaseSeriesLanguageView(LaunchpadView):
+ """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
     translationgroup = None

- def initialize(self):
+ def initialize(self, series, translationgroup):
+ self.series = series
+ self.translationgroup = translationgroup
         self.form = self.request.form

         self.batchnav = BatchNavigator(
@@ -46,14 +52,20 @@
         self.pofiles = self.context.getPOFilesFor(
             self.batchnav.currentBatch())

- @cachedproperty
+ @property
     def translation_group(self):
- """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.translationgroup

     @cachedproperty
     def translation_team(self):
- """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.translation_group is not None:
             team = self.translation_group.query_translator(
                 self.context.language)
@@ -62,98 +74,80 @@
         return team

     @property
- def show_not_logged_in(self):
- """Should we display a notice that user is not logged in?"""
- return self.user is None
+ def access_level_description(self):
+ if self.user is None:
+ return ("You are not logged in. Please log in to work " +
+ "on translations.")

- @property
- def show_no_license(self):
- """Should we display a notice that licence was not accepted?"""
- if self.show_not_logged_in:
- return False
         translations_person = ITranslationsPerson(self.user)
- return not translations_person.translations_relicensing_agreement
-
- @property
- def show_full_edit(self):
- """Should we display a notice that user is not logged in?"""
- if (self.show_not_logged_in or
- self.show_no_license):
- return False
-
- ...

Revision history for this message
Guilherme Salgado (salgado) wrote :
Download full text (15.1 KiB)

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/translations/browser/distroserieslanguage.py' => 'lib/lp/translations/browser/serieslanguage.py'
> --- lib/lp/translations/browser/distroserieslanguage.py 2009-09-17 11:10:49 +0000
> +++ lib/lp/translations/browser/serieslanguage.py 2009-12-11 16:18:29 +0000
>
> +
> +class BaseSeriesLanguageView(LaunchpadView):
[...]
> @@ -47,7 +73,88 @@
> team = None
> return team
>
> + @property
> + def access_level_description(self):
> + 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 = ITranslationsPerson(self.user)
> + translations_contact_link = None
> +
> + if self.translation_team:
> + translations_contact_link = PersonFormatterAPI(
> + self.translation_team.translator).link(None)
> + elif self.translation_group:
> + translations_contact_link = PersonFormatterAPI(
> + self.translation_group.owner).link(None)

Is it possible that self.translation_team and self.translation_group are
both None, thus leaving translations_contact_link set to None as well?
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_contact_link = None"
line.

> +
> + if not translations_person.translations_relicensing_agreement:
> + translation_license_url = PersonFormatterAPI(
> + translations_person).url() + '/translations/+licensing'

Have you tried calling .url(view_name='+licensing') here? In fact, I
think you need that because the URL generated by the code above is wrong
(e.g. ~user/translations/+licensing, which is a 404). If you use the
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='%s'>Translations licensing</a>.") % (
> + translation_license_url)
> +
> + sample_pofile = self.pofiles[0]
> + if sample_pofile is not None:
> + if sample_pofile.canEditTranslations(self.user):
> + return "You can add and review translations."
> +
> + if sample_pofile.canAddSuggestions(self.user):
> + return ("Your suggestions will be held for review by " +
> + "the managers of these translations. If you " +
> + "need help, or your translations are not being " +
> + "...

Revision history for this message
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.

review: Approve (ui)
Revision history for this message
Данило Шеган (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 (SeriesLanguageView) for all ISeriesLanguage-implementing objects (note, this interface doesn't exist yet :), but let's leave that for some other time :)

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 distroserieslanguages and productserieslanguages, but that would make it easy to extend these views to sourcepackagelanguage and projectlanguage views in the future.

Revision history for this message
Adi Roiban (adiroiban) wrote :
Download full text (29.2 KiB)

Î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/translations/browser/distroserieslanguage.py' => 'lib/lp/translations/browser/serieslanguage.py'
> > --- lib/lp/translations/browser/distroserieslanguage.py 2009-09-17 11:10:49 +0000
> > +++ lib/lp/translations/browser/serieslanguage.py 2009-12-11 16:18:29 +0000
> >
> > +
> > +class BaseSeriesLanguageView(LaunchpadView):
> [...]
> > @@ -47,7 +73,88 @@
> > team = None
> > return team
> >
> > + @property
> > + def access_level_description(self):
> > + 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 = ITranslationsPerson(self.user)
> > + translations_contact_link = None
> > +
> > + if self.translation_team:
> > + translations_contact_link = PersonFormatterAPI(
> > + self.translation_team.translator).link(None)
> > + elif self.translation_group:
> > + translations_contact_link = PersonFormatterAPI(
> > + self.translation_group.owner).link(None)
>
> Is it possible that self.translation_team and self.translation_group are
> both None, thus leaving translations_contact_link set to None as well?
> 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_contact_link = None"
> 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_person.translations_relicensing_agreement:
> > + translation_license_url = PersonFormatterAPI(
> > + translations_person).url() + '/translations/+licensing'
>
> Have you tried calling .url(view_name='+licensing') here? In fact, I
> think you need that because the URL generated by the code above is wrong
> (e.g. ~user/translations/+licensing, which is a 404). If you use the
> 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='%s'>Translations licensing</a>.") % (
> > + translation_license_url)
> > +
> > + sample_pofile = self.pofiles[0]
> > + if sample_pofile is not None:
> > + ...

Revision history for this message
Guilherme Salgado (salgado) wrote :
Download full text (9.6 KiB)

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 = ITranslationsPerson(self.user)
> > > + translations_contact_link = None
> > > +
> > > + if self.translation_team:
> > > + translations_contact_link = PersonFormatterAPI(
> > > + self.translation_team.translator).link(None)
> > > + elif self.translation_group:
> > > + translations_contact_link = PersonFormatterAPI(
> > > + self.translation_group.owner).link(None)
> >
> > Is it possible that self.translation_team and self.translation_group are
> > both None, thus leaving translations_contact_link set to None as well?
> > 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_contact_link = None"
> > 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.translation_group is None.") and turn the if/elif into an if/else
with an assert, like this:

    if self.translation_team:
        ...
    else:
        assert self.translation_group is not None, (
            "Must not be called when there's no translation group.")
        ...

> > > +class DistroSeriesLanguageView(BaseSeriesLanguageView, LaunchpadView):
> > > + """View class to render translation status for an `IDistroSeries`."""
> > > +
> > > + def initialize(self):
> > > + series = self.context.distroseries
> > > + super(DistroSeriesLanguageView, self).initialize(
> > > + series=series,
> > > + translationgroup=series.distribution.translationgroup)
> > > + self.parent = self.series.distribution
> > > +
> > > +
> > > +class ProductSeriesLanguageView(BaseSeriesLanguageView, LaunchpadView):
> > > + """View class to render translation status for an `IProductSeries`."""
> > > +
> > > + def initialize(self):
> > > + series = self.context.productseries
> > > + super(ProductSeriesLanguageView, self).initialize(
> > > + series=series,
> >
> > I'd drop the 'series = self.context....' line in both classes above and
> > just pass series=self.context... to super's initialize().
>
> I have leave that attribution to avoid a long line that will follow.
>
> translationgroup=self.context.pro...

Read more...

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

Î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.translation_group is None.") and turn the if/elif into an if/else
> with an assert, like this:
>
> if self.translation_team:
> ...
> else:
> assert self.translation_group is not None, (
> "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_person.translations_relicensing_agreement:
> > translation_license_url = PersonFormatterAPI(
> > - translations_person).url() + '/translations/+licensing'
> > - return ("To make translations in Launchpad you need to " +
> > - "agree with the " +
> > + translations_person).url(
> > + view_name='+licensing',
> > + rootsite='translations')
>
> You might not need rootsite='translations' (as this page is already on
> the translations rootsite), but I might be wrong.
I've checked the implementation for PersonFormatterAPI.url, and as far
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.getLink(
> > + ... 'Ubuntu Spanish Translator',index=0).url
> > + http://launchpad.dev/~ubuntu-l10n-es
> > +
> > + >>> print user_browser.getLink(
> > + ... 'Ubuntu Spanish Translator',index=1).url
> > + http://launchpad.dev/~ubuntu-l10n-es
> > +
> > + >>> print user_browser.getLink(
> > + ... 'Ubuntu Spanish Translator',index=2).url
> > + http://launchpad.dev/~ubuntu-l10n-es
>
> 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/translations/browser/serieslanguage.py'
--- lib/lp/translations/browser/serieslanguage.py 2009-12-11 20:11:06
+0000
+++ lib/lp/translations/browser/serieslanguage.py 2009-12-12 06:19:24
+0000
@@ -75,6 +75,8 @@

     @property
     def access_level_description(self):
+...

Read more...

Revision history for this message
Guilherme Salgado (salgado) :
review: Approve (code)
Revision history for this message
Adi Roiban (adiroiban) wrote :

Hi Salgado,

The failing test is /home/adi/launchpad/lp-branches/bug-427319/lib/lp/translations/browser/tests/test_distroserieslanguage_views.py, line 51, in test_translation_group

    def test_translation_group(self):
        group = self.factory.makeTranslationGroup(
            self.distroseries.distribution.owner, url=None)
        self.distroseries.distribution.translationgroup = group
        self.assertEquals(self.view.translation_group, group)

I tried to change it to the following code... but with no luck

    def test_translation_group(self):
        group = self.factory.makeTranslationGroup(
            self.distroseries.distribution.owner, url=None)
        self.distroseries.distribution.translationgroup = group
        self.view = DistroSeriesLanguageView(
            self.dsl, LaunchpadTestRequest())
        self.assertEquals(self.view.translation_group, group)

--------------------
This is a fix for the view, but maybe I have to fix the test.

=== modified file 'lib/lp/translations/browser/serieslanguage.py'
--- lib/lp/translations/browser/serieslanguage.py 2009-12-12 07:16:07 +0000
+++ lib/lp/translations/browser/serieslanguage.py 2009-12-15 02:25:54 +0000
@@ -38,11 +38,9 @@
     label = "Translatable templates"
     series = None
     parent = None
- translationgroup = None

     def initialize(self, series, translationgroup):
         self.series = series
- self.translationgroup = translationgroup
         self.form = self.request.form

         self.batchnav = BatchNavigator(
@@ -58,7 +56,10 @@

         Return None if there's no translation group for them.
         """
- return self.translationgroup
+ if (self.context.distroseries):
+ return self.context.distroseries.distribution.translationgroup
+ else:
+ return self.context.productseries.product.translationgroup

     @cachedproperty
     def translation_team(self):
@@ -134,25 +135,21 @@
             "translations.")

-class DistroSeriesLanguageView(BaseSeriesLanguageView, LaunchpadView):
+class DistroSeriesLanguageView(BaseSeriesLanguageView):
     """View class to render translation status for an `IDistroSeries`."""

     def initialize(self):
- series = self.context.distroseries
         super(DistroSeriesLanguageView, self).initialize(
- series=series,
- translationgroup=series.distribution.translationgroup)
+ series=self.context.distroseries)
         self.parent = self.series.distribution

-class ProductSeriesLanguageView(BaseSeriesLanguageView, LaunchpadView):
+class ProductSeriesLanguageView(BaseSeriesLanguageView):
     """View class to render translation status for an `IProductSeries`."""

     def initialize(self):
- series = self.context.productseries
         super(ProductSeriesLanguageView, self).initialize(
- series=series,
- translationgroup=series.product.translationgroup)
+ series=self.context.productseries)
         self.context.recalculateCounts()
         self.parent = self.series.product

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

I start a full test.

Here is the diff for the tests.

=== modified file 'lib/lp/translations/browser/tests/test_distroserieslanguage_views.py'
--- lib/lp/translations/browser/tests/test_distroserieslanguage_views.py 2009-12-11 01:03:25 +0000
+++ lib/lp/translations/browser/tests/test_distroserieslanguage_views.py 2009-12-17 15:05:44 +0000
@@ -48,6 +48,8 @@
         group = self.factory.makeTranslationGroup(
             self.distroseries.distribution.owner, url=None)
         self.distroseries.distribution.translationgroup = group
+ self.view = DistroSeriesLanguageView(
+ self.dsl, LaunchpadTestRequest()).initialize()
         self.assertEquals(self.view.translation_group, group)

     def test_translation_team(self):
@@ -65,7 +67,7 @@
             group, self.language, team)
         # Recreate the view because we are using a cached property.
         self.view = DistroSeriesLanguageView(
- self.dsl, LaunchpadTestRequest())
+ self.dsl, LaunchpadTestRequest()).initialize()
         self.assertEquals(self.view.translation_team, translator)

 def test_suite():

=== modified file 'lib/lp/translations/browser/tests/test_productserieslanguage_views.py'
--- lib/lp/translations/browser/tests/test_productserieslanguage_views.py 2009-12-11 16:09:04 +0000
+++ lib/lp/translations/browser/tests/test_productserieslanguage_views.py 2009-12-17 15:07:42 +0000
@@ -137,6 +137,8 @@
         group = self.factory.makeTranslationGroup(
             self.productseries.product.owner, url=None)
         self.productseries.product.translationgroup = group
+ self.view = ProductSeriesLanguageView(
+ self.psl, LaunchpadTestRequest()).initialize()
         self.assertEquals(self.view.translation_group, group)

     def test_translation_team(self):
@@ -154,7 +156,7 @@
             group, self.language, team)
         # Recreate the view because we are using a cached property.
         self.view = ProductSeriesLanguageView(
- self.psl, LaunchpadTestRequest())
+ self.psl, LaunchpadTestRequest()).initialize()
         self.assertEquals(self.view.translation_team, translator)

 def test_suite():

Revision history for this message
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/translations/browser/configure.zcml'
--- lib/lp/translations/browser/configure.zcml 2009-12-12 02:14:16 +0000
+++ lib/lp/translations/browser/configure.zcml 2009-12-17 16:29:14 +0000
@@ -161,9 +161,8 @@
161 attribute_to_parent="distroseries"161 attribute_to_parent="distroseries"
162 rootsite="translations"/>162 rootsite="translations"/>
163 <browser:navigation163 <browser:navigation
164 module="lp.translations.browser.distroserieslanguage"164 module="lp.translations.browser.serieslanguage"
165 classes="165 classes="DistroSeriesLanguageNavigation"/>
166 DistroSeriesLanguageNavigation"/>
167 <browser:defaultView166 <browser:defaultView
168 for="lp.translations.interfaces.distroserieslanguage.IDistroSeriesLanguage"167 for="lp.translations.interfaces.distroserieslanguage.IDistroSeriesLanguage"
169 name="+index"168 name="+index"
@@ -182,7 +181,7 @@
182 permission="zope.Public"181 permission="zope.Public"
183 for="lp.translations.interfaces.distroserieslanguage.IDistroSeriesLanguage"182 for="lp.translations.interfaces.distroserieslanguage.IDistroSeriesLanguage"
184 template="../templates/serieslanguage-index.pt"183 template="../templates/serieslanguage-index.pt"
185 class="lp.translations.browser.distroserieslanguage.DistroSeriesLanguageView"184 class="lp.translations.browser.serieslanguage.DistroSeriesLanguageView"
186 facet="translations"185 facet="translations"
187 layer="canonical.launchpad.layers.TranslationsLayer"/>186 layer="canonical.launchpad.layers.TranslationsLayer"/>
188 <facet187 <facet
@@ -272,7 +271,7 @@
272 attribute_to_parent="productseries"271 attribute_to_parent="productseries"
273 rootsite="translations"/>272 rootsite="translations"/>
274 <browser:navigation273 <browser:navigation
275 module="lp.translations.browser.productserieslanguage"274 module="lp.translations.browser.serieslanguage"
276 classes="275 classes="
277 ProductSeriesLanguageNavigation"/>276 ProductSeriesLanguageNavigation"/>
278 <browser:defaultView277 <browser:defaultView
@@ -292,7 +291,7 @@
292 permission="zope.Public"291 permission="zope.Public"
293 for="lp.translations.interfaces.productserieslanguage.IProductSeriesLanguage"292 for="lp.translations.interfaces.productserieslanguage.IProductSeriesLanguage"
294 template="../templates/serieslanguage-index.pt"293 template="../templates/serieslanguage-index.pt"
295 class="lp.translations.browser.productserieslanguage.ProductSeriesLanguageView"294 class="lp.translations.browser.serieslanguage.ProductSeriesLanguageView"
296 facet="translations"295 facet="translations"
297 layer="canonical.launchpad.layers.TranslationsLayer"/>296 layer="canonical.launchpad.layers.TranslationsLayer"/>
298 <facet297 <facet
299298
=== removed file 'lib/lp/translations/browser/productserieslanguage.py'
--- lib/lp/translations/browser/productserieslanguage.py 2009-09-17 12:45:52 +0000
+++ lib/lp/translations/browser/productserieslanguage.py 1970-01-01 00:00:00 +0000
@@ -1,57 +0,0 @@
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"""Browser code for Product Series Languages."""
5
6__metaclass__ = type
7
8__all__ = [
9 'ProductSeriesLanguageNavigation',
10 'ProductSeriesLanguageView',
11 ]
12
13from canonical.cachedproperty import cachedproperty
14from canonical.launchpad.webapp import LaunchpadView
15from canonical.launchpad.webapp.batching import BatchNavigator
16from canonical.launchpad.webapp.publisher import Navigation
17from lp.translations.interfaces.productserieslanguage import (
18 IProductSeriesLanguage)
19
20
21class ProductSeriesLanguageView(LaunchpadView):
22 """View class to render translation status for an `IProductSeries`."""
23
24 pofiles = None
25 label = "Translatable templates"
26
27 def initialize(self):
28 self.form = self.request.form
29
30 self.batchnav = BatchNavigator(
31 self.context.productseries.getCurrentTranslationTemplates(),
32 self.request)
33
34 self.context.recalculateCounts()
35
36 self.pofiles = self.context.getPOFilesFor(
37 self.batchnav.currentBatch())
38 self.parent = self.context.productseries.product
39
40 @cachedproperty
41 def translation_group(self):
42 return self.context.productseries.product.translationgroup
43
44 @cachedproperty
45 def translation_team(self):
46 """Is there a translation team for this translation."""
47 if self.translation_group is not None:
48 team = self.translation_group.query_translator(
49 self.context.language)
50 else:
51 team = None
52 return team
53
54
55class ProductSeriesLanguageNavigation(Navigation):
56 """Navigation for `IProductSeriesLanguage`."""
57 usedfor = IProductSeriesLanguage
580
=== renamed file 'lib/lp/translations/browser/distroserieslanguage.py' => 'lib/lp/translations/browser/serieslanguage.py'
--- lib/lp/translations/browser/distroserieslanguage.py 2009-09-17 11:10:49 +0000
+++ lib/lp/translations/browser/serieslanguage.py 2009-12-17 16:29:14 +0000
@@ -8,38 +8,64 @@
8__all__ = [8__all__ = [
9 'DistroSeriesLanguageNavigation',9 'DistroSeriesLanguageNavigation',
10 'DistroSeriesLanguageView',10 'DistroSeriesLanguageView',
11 'ProductSeriesLanguageNavigation',
12 'ProductSeriesLanguageView',
11 ]13 ]
1214
15from canonical.cachedproperty import cachedproperty
13from canonical.launchpad.webapp import LaunchpadView16from canonical.launchpad.webapp import LaunchpadView
17from canonical.launchpad.webapp.tales import PersonFormatterAPI
14from canonical.launchpad.webapp.batching import BatchNavigator18from canonical.launchpad.webapp.batching import BatchNavigator
15from canonical.launchpad.webapp.publisher import Navigation19from canonical.launchpad.webapp.publisher import Navigation
16from lp.translations.interfaces.distroserieslanguage import (20from lp.translations.interfaces.distroserieslanguage import (
17 IDistroSeriesLanguage)21 IDistroSeriesLanguage)
1822from lp.translations.interfaces.translationsperson import (
19class DistroSeriesLanguageView(LaunchpadView):23 ITranslationsPerson)
20 """View class to render translation status for an `IDistroSeries`."""24from lp.translations.interfaces.translationgroup import (
25 TranslationPermission)
26from lp.translations.interfaces.productserieslanguage import (
27 IProductSeriesLanguage)
28
29
30class BaseSeriesLanguageView(LaunchpadView):
31 """View base class to render translation status for an
32 `IDistroSeries` and `IProductSeries`
33
34 This class should not be directly instantiated.
35 """
2136
22 pofiles = None37 pofiles = None
23 label = "Translatable templates"38 label = "Translatable templates"
39 series = None
40 parent = None
41 translationgroup = None
2442
25 def initialize(self):43 def initialize(self, series, translationgroup):
44 self.series = series
45 self.translationgroup = translationgroup
26 self.form = self.request.form46 self.form = self.request.form
2747
28 self.batchnav = BatchNavigator(48 self.batchnav = BatchNavigator(
29 self.context.distroseries.getCurrentTranslationTemplates(),49 self.series.getCurrentTranslationTemplates(),
30 self.request)50 self.request)
3151
32 self.pofiles = self.context.getPOFilesFor(52 self.pofiles = self.context.getPOFilesFor(
33 self.batchnav.currentBatch())53 self.batchnav.currentBatch())
34 self.parent = self.context.distroseries.distribution
3554
36 @property55 @property
37 def translation_group(self):56 def translation_group(self):
38 return self.context.distroseries.distribution.translationgroup57 """Return the translation group for these translations.
3958
40 @property59 Return None if there's no translation group for them.
60 """
61 return self.translationgroup
62
63 @cachedproperty
41 def translation_team(self):64 def translation_team(self):
42 """Is there a translation team for this translation."""65 """Return the translation team for these translations.
66
67 Return None if there's no translation team for them.
68 """
43 if self.translation_group is not None:69 if self.translation_group is not None:
44 team = self.translation_group.query_translator(70 team = self.translation_group.query_translator(
45 self.context.language)71 self.context.language)
@@ -47,7 +73,95 @@
47 team = None73 team = None
48 return team74 return team
4975
76 @property
77 def access_level_description(self):
78 """Must not be called when there's no translation group."""
79
80 if self.user is None:
81 return ("You are not logged in. Please log in to work "
82 "on translations.")
83
84 translations_person = ITranslationsPerson(self.user)
85 translations_contact_link = None
86
87 if self.translation_team:
88 translations_contact_link = PersonFormatterAPI(
89 self.translation_team.translator).link(None)
90 elif self.translation_group:
91 translations_contact_link = PersonFormatterAPI(
92 self.translation_group.owner).link(None)
93 else:
94 assert self.translation_group is not None, (
95 "Must not be called when there's no translation group.")
96
97 if not translations_person.translations_relicensing_agreement:
98 translation_license_url = PersonFormatterAPI(
99 self.user).url(
100 view_name='+licensing',
101 rootsite='translations')
102 return ("To make translations in Launchpad you need to "
103 "agree with the "
104 "<a href='%s'>Translations licensing</a>.") % (
105 translation_license_url)
106
107 sample_pofile = self.pofiles[0]
108 if sample_pofile is not None:
109 if sample_pofile.canEditTranslations(self.user):
110 return "You can add and review translations."
111
112 if sample_pofile.canAddSuggestions(self.user):
113 return ("Your suggestions will be held for review by "
114 "%s. If you need help, or your translations are "
115 "not being reviewed, please get in touch with "
116 "%s.") % (
117 translations_contact_link,
118 translations_contact_link)
119
120 permission = sample_pofile.translationpermission
121 if permission == TranslationPermission.CLOSED:
122 return ("These templates can be translated only by "
123 "their managers.")
124
125 if self.translation_team is None:
126 return ("Since there is nobody to manage translation "
127 "approvals into this language, your cannot add "
128 "new suggestions. If you are interested in making "
129 "translations, please contact %s.") % (
130 translations_contact_link)
131
132 raise AssertionError(
133 "BUG! Couldn't identify the user's access level for these "
134 "translations.")
135
136
137class DistroSeriesLanguageView(BaseSeriesLanguageView):
138 """View class to render translation status for an `IDistroSeries`."""
139
140 def initialize(self):
141 series = self.context.distroseries
142 super(DistroSeriesLanguageView, self).initialize(
143 series=series,
144 translationgroup=series.distribution.translationgroup)
145 self.parent = self.series.distribution
146
147
148class ProductSeriesLanguageView(BaseSeriesLanguageView):
149 """View class to render translation status for an `IProductSeries`."""
150
151 def initialize(self):
152 series = self.context.productseries
153 super(ProductSeriesLanguageView, self).initialize(
154 series=series,
155 translationgroup=series.product.translationgroup)
156 self.context.recalculateCounts()
157 self.parent = self.series.product
158
50159
51class DistroSeriesLanguageNavigation(Navigation):160class DistroSeriesLanguageNavigation(Navigation):
52 """Navigation for `IDistroSeriesLanguage`."""161 """Navigation for `IDistroSeriesLanguage`."""
53 usedfor = IDistroSeriesLanguage162 usedfor = IDistroSeriesLanguage
163
164
165class ProductSeriesLanguageNavigation(Navigation):
166 """Navigation for `IProductSeriesLanguage`."""
167 usedfor = IProductSeriesLanguage
54168
=== modified file 'lib/lp/translations/browser/tests/test_distroserieslanguage_views.py'
--- lib/lp/translations/browser/tests/test_distroserieslanguage_views.py 2009-09-11 06:57:21 +0000
+++ lib/lp/translations/browser/tests/test_distroserieslanguage_views.py 2009-12-17 16:29:14 +0000
@@ -8,7 +8,7 @@
88
9from zope.component import getUtility9from zope.component import getUtility
1010
11from lp.translations.browser.distroserieslanguage import (11from lp.translations.browser.serieslanguage import (
12 DistroSeriesLanguageView)12 DistroSeriesLanguageView)
13from lp.translations.interfaces.translator import ITranslatorSet13from lp.translations.interfaces.translator import ITranslatorSet
14from canonical.launchpad.webapp.servers import LaunchpadTestRequest14from canonical.launchpad.webapp.servers import LaunchpadTestRequest
@@ -48,6 +48,9 @@
48 group = self.factory.makeTranslationGroup(48 group = self.factory.makeTranslationGroup(
49 self.distroseries.distribution.owner, url=None)49 self.distroseries.distribution.owner, url=None)
50 self.distroseries.distribution.translationgroup = group50 self.distroseries.distribution.translationgroup = group
51 self.view = DistroSeriesLanguageView(
52 self.dsl, LaunchpadTestRequest())
53 self.view.initialize()
51 self.assertEquals(self.view.translation_group, group)54 self.assertEquals(self.view.translation_group, group)
5255
53 def test_translation_team(self):56 def test_translation_team(self):
@@ -66,6 +69,7 @@
66 # Recreate the view because we are using a cached property.69 # Recreate the view because we are using a cached property.
67 self.view = DistroSeriesLanguageView(70 self.view = DistroSeriesLanguageView(
68 self.dsl, LaunchpadTestRequest())71 self.dsl, LaunchpadTestRequest())
72 self.view.initialize()
69 self.assertEquals(self.view.translation_team, translator)73 self.assertEquals(self.view.translation_team, translator)
7074
71def test_suite():75def test_suite():
7276
=== modified file 'lib/lp/translations/browser/tests/test_productserieslanguage_views.py'
--- lib/lp/translations/browser/tests/test_productserieslanguage_views.py 2009-07-17 00:26:05 +0000
+++ lib/lp/translations/browser/tests/test_productserieslanguage_views.py 2009-12-17 16:29:14 +0000
@@ -7,7 +7,7 @@
77
8from zope.component import getUtility8from zope.component import getUtility
99
10from lp.translations.browser.productserieslanguage import (10from lp.translations.browser.serieslanguage import (
11 ProductSeriesLanguageView)11 ProductSeriesLanguageView)
12from lp.translations.interfaces.translator import ITranslatorSet12from lp.translations.interfaces.translator import ITranslatorSet
13from canonical.launchpad.webapp.servers import LaunchpadTestRequest13from canonical.launchpad.webapp.servers import LaunchpadTestRequest
@@ -137,6 +137,7 @@
137 group = self.factory.makeTranslationGroup(137 group = self.factory.makeTranslationGroup(
138 self.productseries.product.owner, url=None)138 self.productseries.product.owner, url=None)
139 self.productseries.product.translationgroup = group139 self.productseries.product.translationgroup = group
140 self.view.initialize()
140 self.assertEquals(self.view.translation_group, group)141 self.assertEquals(self.view.translation_group, group)
141142
142 def test_translation_team(self):143 def test_translation_team(self):
@@ -155,6 +156,7 @@
155 # Recreate the view because we are using a cached property.156 # Recreate the view because we are using a cached property.
156 self.view = ProductSeriesLanguageView(157 self.view = ProductSeriesLanguageView(
157 self.psl, LaunchpadTestRequest())158 self.psl, LaunchpadTestRequest())
159 self.view.initialize()
158 self.assertEquals(self.view.translation_team, translator)160 self.assertEquals(self.view.translation_team, translator)
159161
160def test_suite():162def test_suite():
161163
=== renamed file 'lib/lp/translations/stories/productseries/xx-productserieslanguage.txt' => 'lib/lp/translations/stories/standalone/xx-serieslanguage-index.txt'
--- lib/lp/translations/stories/productseries/xx-productserieslanguage.txt 2009-09-12 07:25:21 +0000
+++ lib/lp/translations/stories/standalone/xx-serieslanguage-index.txt 2009-12-17 16:29:14 +0000
@@ -1,4 +1,9 @@
1= Product release series language overview =1Product or distribution release series language overview
2========================================================
3
4These pages are used by translators for accessing all templates in a
5release series and viewing translation statistics for each template for
6a specific language
27
3The Translations page for a product release series with multiple8The Translations page for a product release series with multiple
4templates links to per-language overviews.9templates links to per-language overviews.
@@ -11,4 +16,187 @@
11 >>> print browser.title16 >>> print browser.title
12 Portuguese (Brazil) (pt_BR) : Translations : Series trunk : Evolution17 Portuguese (Brazil) (pt_BR) : Translations : Series trunk : Evolution
1318
19Since there is no translation team to manage Portuguese (Brazil) language
20in the Evolution's translation group, all users will be informed about it
21and pointed to the translation group owner.
22
23 >>> print extract_text(find_tag_by_id(
24 ... browser.contents, 'group-team-info'))
25 There is no team to manage Evolution ... translations to ...
26 To set one up, please get in touch with Carlos Perelló Marín.
27
28Anonymous users are informed that in order to make translations they
29need to login first.
30
31 >>> print extract_text(
32 ... find_tag_by_id(browser.contents, 'translation-access-level'))
33 You are not logged in. Please log in to work on translations...
34
35Authenticated users will see information about what they can do in
36these translations. Things like review, only add suggestion or no
37changes at all.
38
39If a product or distribution had no translation group, visitors are
40informed about this fact and will be able to add translations without
41requiring a review.
42
43 >>> user_browser.open(
44 ... 'http://translations.launchpad.dev/ubuntu/hoary/+lang/es')
45 >>> print extract_text(
46 ... find_tag_by_id(user_browser.contents, 'group-team-info'))
47 There is no translation group to manage Ubuntu translations.
48
49Create a translation group for Ubuntu, together with a translation
50person for managing Ubuntu Spanish translations and set translation
51policy to RESTRICTED.
52This is done to so see what the page will look like when they exist.
53
54 >>> from zope.component import getUtility
55 >>> from canonical.launchpad.interfaces import ILaunchpadCelebrities
56 >>> from lp.translations.interfaces.translationgroup import (
57 ... TranslationPermission)
58 >>> login('foo.bar@canonical.com')
59 >>> utc_owner = factory.makePerson(displayname='Some Guy')
60 >>> utc_team = factory.makeTeam(
61 ... owner=utc_owner, name='utc-team',
62 ... displayname='Ubuntu Translation Coordinators')
63 >>> utg = factory.makeTranslationGroup(
64 ... owner=utc_team, name='utg', title='Ubuntu Translation Group')
65 >>> st_coordinator = factory.makePerson(
66 ... name="ubuntu-l10n-es",
67 ... displayname='Ubuntu Spanish Translators')
68 >>> dude = factory.makePerson(
69 ... name="dude", email="dude@ex.com", password="test")
70 >>> ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
71 >>> ubuntu.translationgroup = utg
72 >>> ubuntu.translationpermission = TranslationPermission.RESTRICTED
73 >>> translators = factory.makeTranslator(
74 ... 'es', group=utg, person=st_coordinator)
75 >>> no_license_translator = factory.makeTranslator(
76 ... 'es', person=dude, license=False)
77 >>> logout()
78
79Spanish has a translation team for managing its translations and all
80Evolution Spanish templates can be accessed from the distribution series
81translation page.
82
83 >>> user_browser.open(
84 ... 'http://translations.launchpad.dev/ubuntu/hoary/')
85 >>> user_browser.getLink('Spanish').click()
86 >>> print user_browser.url
87 http://translations.launchpad.dev/ubuntu/hoary/+lang/es
88
89 >>> print extract_text(
90 ... find_tag_by_id(user_browser.contents, 'group-team-info'))
91 These Ubuntu translations are managed by Ubuntu Spanish Translators.
92
93Authenticated users can add suggestion but will be held for review by
94the members of Spanish translations team.
95
96 >>> print extract_text(
97 ... find_tag_by_id(
98 ... user_browser.contents, 'translation-access-level'))
99 Your suggestions will be held for review by
100 Ubuntu Spanish Translator...
101 please get in touch with Ubuntu Spanish Translators...
102
103Users will see three references to the team managing these translations.
104
105 >>> print user_browser.getLink(
106 ... 'Ubuntu Spanish Translator').url
107 http://launchpad.dev/~ubuntu-l10n-es
108
109Catalan has no translation team for managing translations and since
110there is no one to review the work, authenticated users can not add
111suggestions.
112
113 >>> user_browser.open(
114 ... 'http://translations.launchpad.dev/ubuntu/hoary/')
115 >>> user_browser.getLink('Catalan').click()
116 >>> print user_browser.url
117 http://translations.launchpad.dev/ubuntu/hoary/+lang/ca
118
119 >>> print extract_text(
120 ... find_tag_by_id(user_browser.contents, 'group-team-info'))
121 There is no team to manage ... To set one up, please get in touch
122 with Ubuntu Translation Coordinators.
123
124 >>> print extract_text(find_tag_by_id(
125 ... user_browser.contents, 'translation-access-level'))
126 Since there is nobody to manage translation ...
127 your cannot add new suggestions. If you are interested in making
128 translations, please contact Ubuntu Translation Coordinators...
129
130 >>> print user_browser.getLink(
131 ... 'Ubuntu Translation Coordinators').url
132 http://launchpad.dev/~utc-team
133
134Members of translation team and translations admins have full access to
135translations. They can add and review translations.
136
137 >>> admin_browser.open(
138 ... 'http://translations.launchpad.dev/ubuntu/hoary/+lang/ro')
139 >>> print extract_text(find_tag_by_id(
140 ... admin_browser.contents, 'translation-access-level'))
141 You can add and review translations...
142
143For projects using closed translations policy, a translator that is not
144member of the translation team appointed for that language will not
145be allowed to make any changes.
146
147 >>> login('foo.bar@canonical.com')
148 >>> ubuntu.translationpermission = TranslationPermission.CLOSED
149 >>> logout()
150
151 >>> user_browser.open(
152 ... 'http://translations.launchpad.dev/ubuntu/hoary/+lang/ro')
153 >>> print extract_text(find_tag_by_id(
154 ... user_browser.contents, 'translation-access-level'))
155 These templates can be translated only by their managers...
156
157Translation policy is rolled back to not affect other tests.
158
159 >>> login('foo.bar@canonical.com')
160 >>> ubuntu.translationpermission = TranslationPermission.RESTRICTED
161 >>> logout()
162
163Translators that have not agreed with the license can not make
164translations, and will see a link to the license page.
165
166 >>> no_license_browser = setupBrowser(
167 ... auth='Basic dude@ex.com:test')
168 >>> no_license_browser.open(
169 ... 'http://translations.launchpad.dev/ubuntu/hoary/+lang/ro')
170 >>> print extract_text(find_tag_by_id(
171 ... no_license_browser.contents, 'translation-access-level'))
172 To make translations in Launchpad you need to agree with
173 the Translations licensing...
174
175 >>> print no_license_browser.getLink(
176 ... 'Translations licensing').url
177 http://translations.launchpad.dev/~dude/+licensing
178
179For project with no translation group, translators see a note stating
180this fact. No access level information is displayed.
181
182 >>> login('foo.bar@canonical.com')
183 >>> ubuntu.translationgroup = None
184 >>> logout()
185
186 >>> user_browser.open(
187 ... 'http://translations.launchpad.dev/ubuntu/hoary/+lang/ro')
188 >>> print extract_text(
189 ... find_tag_by_id(user_browser.contents, 'group-team-info'))
190 There is no translation group to manage Ubuntu translations.
191
192 >>> print extract_text(find_tag_by_id(
193 ... user_browser.contents, 'translation-access-level'))
194 Templates which are more important to translate are listed first.
195
196Translation group configuration is rolled back to not affect other tests.
197
198 >>> login('foo.bar@canonical.com')
199 >>> ubuntu.translationgroup = utg
200 >>> logout()
201
14The details of the page are tested at the view level.202The details of the page are tested at the view level.
15203
=== modified file 'lib/lp/translations/templates/serieslanguage-index.pt'
--- lib/lp/translations/templates/serieslanguage-index.pt 2009-09-17 11:10:49 +0000
+++ lib/lp/translations/templates/serieslanguage-index.pt 2009-12-17 16:29:14 +0000
@@ -16,33 +16,45 @@
1616
17 <tal:block condition="view/pofiles">17 <tal:block condition="view/pofiles">
18 <div class="top-portlet">18 <div class="top-portlet">
19 <tal:with_group condition="view/translation_group">19 <div id="group-team-info">
20 <p tal:condition="view/translation_team">20 <tal:with_group condition="view/translation_group">
21 <tal:product replace="structure21 <p tal:condition="view/translation_team">
22 view/parent/fmt:link">Evolution</tal:product>22 These
23 is translated by23 <tal:product replace="structure
24 <tal:team replace="structure24 view/parent/fmt:link">Evolution</tal:product>
25 view/translation_team/translator/fmt:link">25 translations are managed by
26 Serbian translators</tal:team> — if you need help, or your26 <tal:team replace="structure
27 translations are not being reviewed, please get in touch with them.27 view/translation_team/translator/fmt:link">
28 </p>28 Serbian translators</tal:team>.
2929 </p>
30 <p tal:condition="not:view/translation_team">30
31 There is no team to translate31 <p tal:condition="not:view/translation_team">
32 <tal:product replace="structure32 There is no team to manage
33 view/parent/fmt:link">Evolution</tal:product>33 <tal:product replace="structure
34 to34 view/parent/fmt:link">Evolution</tal:product>
35 <a tal:attributes="href context/language/fmt:url"35 translations to
36 tal:content="context/language/englishname">Serbian</a>.36 <a tal:attributes="href context/language/fmt:url"
37 To set one up, please get in touch with37 tal:content="context/language/englishname">Serbian</a>.
38 <a tal:replace="structure view/translation_group/fmt:link"38 To set one up, please get in touch with
39 >Launchpad Translators</a>39 <a tal:replace="structure view/translation_group/owner/fmt:link"
40 coordinator.40 >Launchpad Translators</a>.
41 </p>41 </p>
42 </tal:with_group>42 </tal:with_group>
4343 <tal:without_group condition="not: view/translation_group">
44 <p>44 <p>
45 Choose a template name to begin translating.45 There is no translation group to manage
46 <tal:product replace="structure
47 view/parent/fmt:link">Evolution</tal:product>
48 translations.
49 </p>
50 </tal:without_group>
51 </div>
52 <p id="translation-access-level">
53 <span
54 tal:condition="view/translation_group"
55 tal:replace="structure view/access_level_description">
56 You can add suggestions for these translations.
57 </span>
46 Templates which are more important to translate are listed first.58 Templates which are more important to translate are listed first.
47 </p>59 </p>
48 </div>60 </div>