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

Proposed by Adi Roiban
Status: Merged
Approved by: Данило Шеган
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~adiroiban/launchpad/bug-406477
Merge into: lp:launchpad
Diff against target: 708 lines (+380/-101)
11 files modified
lib/canonical/launchpad/icing/style-3-0.css (+13/-0)
lib/canonical/launchpad/security.py (+74/-33)
lib/lp/testing/factory.py (+4/-1)
lib/lp/translations/browser/configure.zcml (+1/-1)
lib/lp/translations/browser/potemplate.py (+2/-2)
lib/lp/translations/configure.zcml (+1/-1)
lib/lp/translations/stories/distroseries/xx-distroseries-templates.txt (+72/-15)
lib/lp/translations/stories/standalone/xx-potemplate-admin.txt (+42/-2)
lib/lp/translations/stories/standalone/xx-potemplate-index.txt (+70/-1)
lib/lp/translations/templates/object-templates.pt (+100/-44)
lib/lp/translations/templates/potemplate-index.pt (+1/-1)
To merge this branch: bzr merge lp:~adiroiban/launchpad/bug-406477
Reviewer Review Type Date Requested Status
Данило Шеган (community) Approve
Review via email: mp+15793@code.launchpad.net

Commit message

Use launchpad.TranslationsAdmin privileges for IPOTemplate and IPOTemplateSubset. Allow owners of distribution translation group to also administer disabled templates.

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

= Bug 406477 =

Members of the ubuntu-translations-coordinators team can edit templates through the +admin pages.

However, once a template has been disabled, the +admin page is no longer accessible to them, which stops them from e.g. enabling the template again in case of a mistake.

== Implementation details ==

After asking a pre-implementation review from Danilo, he suggest I should use launchpad.TranslationsAdmin, instead of the more generic launchpad.Admin translation.

The previous administration privileges for IPOTemplate was assigned only to RosettaAdmins.
Now it was change to all RosettaAdmin + owner of distro.translationgroup

== Tests ==

There were no test covering the use case of distribution translations coordinator admin rights to templates, so this was added to distroseries-templates

Also the previous rosetta-potemplate-index was not checking the administrative links.

./bin/test -ct "distroseries-templates"
./bin/test -ct "rosetta-potemplate-index"

== Demo and Q/A ==

Make sure you are a member of Ubuntu Translation Coordinators team (ubuntu-l10n-coordinator).
https://launchpad.dev/~ubuntu-l10n-coordinator

Go to the Distro +template page
https://translations.launchpad.dev/ubuntu/hoary/+templates

You should see the Administer page for Evolution, disabled-template .

As a member of Ubuntu Translation Coordinators team you should be able to administer it, just like any other template from that table.

Login as a normal user, you should not see the administration links (only Download), and when trying to manually enter the admin url (https://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/disabled-template/+admin) you should see an access denied page.

= Launchpad lint =

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

Linting changed files:
  lib/canonical/launchpad/security.py
  lib/lp/translations/browser/configure.zcml
  lib/lp/translations/browser/potemplate.py
  lib/lp/translations/stories/distroseries/xx-distroseries-templates.txt
  lib/lp/translations/stories/standalone/xx-rosetta-potemplate-index.txt
  lib/lp/translations/templates/object-templates.pt
  lib/lp/translations/templates/potemplate-index.pt

Revision history for this message
Данило Шеган (danilo) wrote :
Download full text (14.0 KiB)

Adi, thanks a lot for working on this. In general, there's one major
complaint about how you implemented permissions. There's a number of
narrative/stylistic suggestions as well, though they are obviously of
lower priority (though still important).

If I sound patronising anywhere, please forgive me: I am not sure how
much of our code base or styling guides you have already had a chance to
figure out yourself, so I might be saying things you already know :)

Anyway, this would need further improvements, but is overall a great
step in the right direction!

  review needsfixing

У уто, 08. 12 2009. у 03:15 +0000, Adi Roiban пише:

> == Implementation details ==
>
> After asking a pre-implementation review from Danilo, he suggest I
> should use launchpad.TranslationsAdmin, instead of the more generic
> launchpad.Admin translation.
>
> The previous administration privileges for IPOTemplate was assigned
> only to RosettaAdmins.
> Now it was change to all RosettaAdmin + owner of
> distro.translationgroup
>
> == Tests ==
>
> There were no test covering the use case of distribution translations
> coordinator admin rights to templates, so this was added to
> distroseries-templates
>
> Also the previous rosetta-potemplate-index was not checking the
> administrative links.

FWIW, sometimes we avoid having too many tests in "pagetests" (i.e.
stories), because they are very fragile. Just moving stuff around in
the layout sometimes breaks them, so we do not test for all possible
cases in them.

> ./bin/test -ct "distroseries-templates"
> ./bin/test -ct "rosetta-potemplate-index"

FWIW, you can pass -t to bin/test multiple times, eg.

  bin/test -vvct distroseries-templates -t rosetta-potemplate-index

(if you really must have colorized output; -vv is basically a standard
verbose mode LP developers use, and -vvv gives you test timings as well)

> == Demo and Q/A ==
>
> Make sure you are a member of Ubuntu Translation Coordinators team
> (ubuntu-l10n-coordinator).
> https://launchpad.dev/~ubuntu-l10n-coordinator
>
> Go to the Distro +template page
> https://translations.launchpad.dev/ubuntu/hoary/+templates
>
> You should see the Administer page for Evolution, disabled-template .
>
> As a member of Ubuntu Translation Coordinators team you should be able
> to administer it, just like any other template from that table.
>
> Login as a normal user, you should not see the administration links
> (only Download), and when trying to manually enter the admin url
> (https://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/disabled-template/+admin) you should see an access denied page.

While playing with it, I discovered that you see disabled templates even
if you are not logged in, or if you don't have sufficient privileges.
Clicking on it gives you a 'not found' error. It's probably not your
code, but it'd be useful to fix it along the way and show disabled
templates only if someone has TranslationsAdmin privilege on them.

> === modified file 'lib/canonical/launchpad/security.py'
> --- lib/canonical/launchpad/security.py 2009-11-19 15:14:53 +0000
> +++ lib/canonical/launchpad/security.py 2009-12-08 03:15:30 +0000
> @@ -479,6 +479,28 @@...

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

Hi,

Thank you very much for taking the time and reviewing this branch.
It was really ugly. So don't worry, I deserve it :)

I agree that is hard to cover all test paths, is just that right now I don't know what should be tested and what not, and where is the border between a useful test and a bloated test.
Is a test is not needed, feel free to comment and I will delete it.

I'm testing using a single command line, but in MP I put them in different lines to improve readability... stupid thing to do :). For the future I'll put them in a single line.

Disabled templates are only displayed for TranslationsAdmins.
The previous behavior was validated by test stories.

I didn't knew Ubuntu is already a celebrity.
I prefer to keep with Ubuntu, without creating a new distribution. It will just make the story to complicated.
Added a "license=yes" to MakeTranslator , to also sign the translation license.. otherwise that translator has no edit rights.

I'm sorry for failing to write proper stories. Hope I can improve my narrator skills.

Hope the permission are right.

I added stories for testing administration and editing of POTemplate and PoTemplateSubset.

Thank you again for taking the time reviewing this branch.
If there are further issues, have no mercy! :)

It is ok to have these filenames?
xx-potemplate-admin.txt
xx-potemplate-edit.txt
xx-rosetta-potemplate-export.txt
xx-rosetta-potemplate-index.txt

I was thinking at removing rosetta from the filename.

Revision history for this message
Данило Шеган (danilo) wrote :
Download full text (18.7 KiB)

Hi Adi,

Thanks for all the effort you are putting into this: you are not just
solving the bug, you are helping improve the Launchpad code quality as
well (which is why it's a bit harder than expected :). There's still
some work needed on this branch.

У сре, 09. 12 2009. у 01:03 +0000, Adi Roiban пише:

> Thank you very much for taking the time and reviewing this branch.
> It was really ugly. So don't worry, I deserve it :)

You are most welcome - and it wasn't ugly, it just needed further
polish :)

Btw, it's useful to post an incremental diff (i.e. only the changes
since the last review) so it's easier for a reviewer to only go over
that without going through things they have already approved. This
time it's simple because it's only one revision, but sometimes it's
harder than that and then an incremental diff helps.

> I'm testing using a single command line, but in MP I put them in
> different lines to improve readability... stupid thing to do :). For
> the future I'll put them in a single line.

Thanks!

> Disabled templates are only displayed for TranslationsAdmins.
> The previous behavior was validated by test stories.

Cool, thanks for this improvement!

> I didn't knew Ubuntu is already a celebrity.
> I prefer to keep with Ubuntu, without creating a new
> distribution. It will just make the story to complicated.

Well, "complicated" is a relative term. If you create a new
distribution and at least two new templates (one disabled, another
enabled), you'd have a fully self-contained test. That's means that
extra 5 lines of test set-up, you'll get a story which a reader can
understand fully without resorting to "make run" to see what data is
already there.

However, this branch is already too big: do not worry about this and
keep it as-is. For future work, I do suggest you keep the above in
mind.

> Added a "license=yes" to MakeTranslator , to also sign the
> translation license.. otherwise that translator has no edit rights.

Cool, thanks for doing this.

> I'm sorry for failing to write proper stories. Hope I can improve my
> narrator skills.

Don't worry about it. None of us is great at it, and we've got
years of experience. With every review though, we are always getting
better :)

There are still some suggestions on how to improve it, and some tips
on how to think about it when writing stories inline below.

> Hope the permission are right.

Not yet. I'll comment on the actual code below.

> I added stories for testing administration and editing of POTemplate
> and PoTemplateSubset.

Thanks, they look good.

> Thank you again for taking the time reviewing this branch.
> If there are further issues, have no mercy! :)

This is complicated stuff, so don't feel bad if I have no mercy :)

> It is ok to have these filenames?
> xx-potemplate-admin.txt
> xx-potemplate-edit.txt
> xx-rosetta-potemplate-export.txt
> xx-rosetta-potemplate-index.txt
>
> I was thinking at removing rosetta from the filename.

Sure, that'd be great :)

And now, your code with my comments inline:

=== modified file 'lib/canonical/launchpad/security.py'
> --- lib/canonical/launchpad/security.py 2009-12-07 21:58:53 +0000
> +++ lib/canonical/launchpad/secur...

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

Here is the diff from the last commit.
I will leave the other tasks for another branch.

I looked for all the other places where factory.makeTranslator is used and the change should not affect the current tests.

bin/test -m translations looks ok.

=== modified file 'lib/canonical/launchpad/icing/style-3-0.css'
--- lib/canonical/launchpad/icing/style-3-0.css 2009-12-05 03:11:48 +0000
+++ lib/canonical/launchpad/icing/style-3-0.css 2009-12-10 12:03:04 +0000
@@ -674,6 +674,18 @@
     padding: 2px 1em 2px 2px;
 }

+/* Templates listing.
+ *
+ * Examples:
+ * https://translations.launchpad.dev/ubuntu/hoary/+templates
+ * https://translations.launchpad.dev/evolution/trunk/+templates
+ */
+
+.inactive-template td {
+ background-color: #DCDCDC;
+}
+
+
 /* Translations statistics and legend.
  *
  * Examples:

=== modified file 'lib/canonical/launchpad/security.py'
--- lib/canonical/launchpad/security.py 2009-12-09 01:03:22 +0000
+++ lib/canonical/launchpad/security.py 2009-12-10 11:31:11 +0000
@@ -1123,13 +1123,21 @@
         to edit distribution details are able to change translation settings
         for a distribution.
         """
- return (
+ # Translation group owner for a distribution is also a
+ # translations administrator for it.
+ translation_group = self.obj.translationgroup
+ if translation_group and user.inTeam(translation_group.owner):
+ return True
+ else:
+ return (
             OnlyRosettaExpertsAndAdmins.checkAuthenticated(self, user) or
             EditDistributionByDistroOwnersOrAdmins.checkAuthenticated(
                 self, user))

-class AdminPOTemplateDetails(AdminDistributionTranslations):
+# Please keep AdminPOTemplateSubset in sync with this, unless you
+# know exactly what you are doing.
+class AdminPOTemplateDetails(OnlyRosettaExpertsAndAdmins):
     """Controls administration of an `IPOTemplate`.

     Allow all persons that can also administer the translations to
@@ -1143,16 +1151,17 @@
     def checkAuthenticated(self, user):
         template = self.obj
         if template.distroseries is not None:
- distro = template.distroseries.distribution
- translation_group = distro.translationgroup
- if translation_group and user.inTeam(translation_group.owner):
- return True
-
+ # Template is on a distribution.
+ distribution = template.distroseries.distribution
             return (
                 AdminDistributionTranslations(
- template.distroseries.distribution).checkAuthenticated(user))
+ template.distroseries.distribution).checkAuthenticated(
+ user))

- return False
+ else:
+ # Template is on a product.
+ return OnlyRosettaExpertsAndAdmins.checkAuthenticated(
+ self, user)

 class EditPOTemplateDetails(AdminPOTemplateDetails, EditByOwnersOrAdmins):
@@ -1636,7 +1645,11 @@
                 user.inTeam(celebs.bazaar_experts))

-class AdminPOTemplateSubset(AdminDistributionTranslations):
+# Please keep this in sync with AdminPOTemplateDetails. Note that
+# th...

Revision history for this message
Данило Шеган (danilo) wrote :
Download full text (5.4 KiB)

У чет, 10. 12 2009. у 12:44 +0000, Adi Roiban пише:
> Here is the diff from the last commit.
> I will leave the other tasks for another branch.

Cool, thanks. We are almost there, only a few bits remaining.

> === modified file 'lib/canonical/launchpad/icing/style-3-0.css'
> --- lib/canonical/launchpad/icing/style-3-0.css 2009-12-05 03:11:48 +0000
> +++ lib/canonical/launchpad/icing/style-3-0.css 2009-12-10 12:03:04 +0000
> @@ -674,6 +674,18 @@
> padding: 2px 1em 2px 2px;
> }
>
> +/* Templates listing.
> + *
> + * Examples:
> + * https://translations.launchpad.dev/ubuntu/hoary/+templates
> + * https://translations.launchpad.dev/evolution/trunk/+templates
> + */
> +
> +.inactive-template td {
> + background-color: #DCDCDC;
> +}
> +
> +

I tried it out: this looks great. I've also played with

.inactive-template td {
    background-color: #fee;
    color: #855;
}

(to give it a slight redish appearance, to indicate something is wrong
with it), but I am not so sure about it. Your call about that, but if
you are in doubt about something, JFDI. :)

> === modified file 'lib/canonical/launchpad/security.py'
> --- lib/canonical/launchpad/security.py 2009-12-09 01:03:22 +0000
> +++ lib/canonical/launchpad/security.py 2009-12-10 11:31:11 +0000

> @@ -1123,13 +1123,21 @@
> to edit distribution details are able to change translation settings
> for a distribution.
> """
> - return (
> + # Translation group owner for a distribution is also a
> + # translations administrator for it.
> + translation_group = self.obj.translationgroup
> + if translation_group and user.inTeam(translation_group.owner):
> + return True
> + else:
> + return (
> OnlyRosettaExpertsAndAdmins.checkAuthenticated(self, user) or
> EditDistributionByDistroOwnersOrAdmins.checkAuthenticated(
> self, user))

A minor nitpick: please fix the indentation here as well. In general,
if you are using Emacs, python-mode should do it correctly for you if
you just press Tab key once on each of these lines.

> +# Please keep AdminPOTemplateSubset in sync with this, unless you
> +# know exactly what you are doing.
> +class AdminPOTemplateDetails(OnlyRosettaExpertsAndAdmins):
> """Controls administration of an `IPOTemplate`.
>
> Allow all persons that can also administer the translations to
> @@ -1143,16 +1151,17 @@
> def checkAuthenticated(self, user):
> template = self.obj
> if template.distroseries is not None:
> - distro = template.distroseries.distribution
> - translation_group = distro.translationgroup
> - if translation_group and
> user.inTeam(translation_group.owner):
> - return True
> -
> + # Template is on a distribution.
> + distribution = template.distroseries.distribution
> return (
> AdminDistributionTranslations(
> -
> template.distroseries.distribution).checkAuthenticated(user))
> +
> template.distroseries.distribution).checkAuthenticated(
> + user))

Let's replace this `template.d...

Read more...

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

Many thanks for the review... there we a couple of stupid errors :(
For the future I will try to read the whole diff at least twice.

Latest diff.

=== modified file 'lib/canonical/launchpad/icing/style-3-0.css'
--- lib/canonical/launchpad/icing/style-3-0.css 2009-12-10 12:46:11 +0000
+++ lib/canonical/launchpad/icing/style-3-0.css 2009-12-11 11:33:39 +0000
@@ -682,7 +682,8 @@
  */

 .inactive-template td {
- background-color: #DCDCDC;
+ background-color: #fee;
+ color: #855;
 }

=== modified file 'lib/canonical/launchpad/security.py'
--- lib/canonical/launchpad/security.py 2009-12-10 12:46:11 +0000
+++ lib/canonical/launchpad/security.py 2009-12-11 11:33:39 +0000
@@ -1130,9 +1130,9 @@
             return True
         else:
             return (
- OnlyRosettaExpertsAndAdmins.checkAuthenticated(self, user) or
- EditDistributionByDistroOwnersOrAdmins.checkAuthenticated(
- self, user))
+ OnlyRosettaExpertsAndAdmins.checkAuthenticated(self, user) or
+ EditDistributionByDistroOwnersOrAdmins.checkAuthenticated(
+ self, user))

 # Please keep AdminPOTemplateSubset in sync with this, unless you
@@ -1155,8 +1155,7 @@
             distribution = template.distroseries.distribution
             return (
                 AdminDistributionTranslations(
- template.distroseries.distribution).checkAuthenticated(
- user))
+ distribution).checkAuthenticated(user))

         else:
             # Template is on a product.
@@ -1663,16 +1662,13 @@
     def checkAuthenticated(self, user):
         template_set = self.obj
         if template_set.distroseries is not None:
- distro = template_set.distroseries.distribution
- translation_group = distro.translationgroup
- if translation_group and user.inTeam(translation_group.owner):
- return True
-
+ distribution = template_set.distroseries.distribution
             return (
                 AdminDistributionTranslations(
- template_set.distroseries.distribution).checkAuthenticated(user))
-
- return False
+ distribution).checkAuthenticated(user))
+ else:
+ # Template is on a product.
+ return OnlyRosettaExpertsAndAdmins.checkAuthenticated(self, user)

 class AdminDistroSeriesLanguage(OnlyRosettaExpertsAndAdmins):

=== modified file 'lib/lp/translations/stories/standalone/xx-potemplate-admin.txt'
--- lib/lp/translations/stories/standalone/xx-potemplate-admin.txt 2009-08-12 05:09:36 +0000
+++ lib/lp/translations/stories/standalone/xx-potemplate-admin.txt 2009-12-11 11:33:39 +0000
@@ -1,3 +1,10 @@
+Administering POTemplates
+=========================
+
+
+Product templates
+-----------------
+
 The POTemplate admin page lets us to edit any aspect of that object, that's
 why we need to be a Rosetta Expert or a Launchpad admin to use it.

@@ -155,8 +162,29 @@
   >>> print admin_browser.url
   http://translations.launchpad.dev/evolution/trunk/+pots/evolution-renamed

-
-== Distribution templates ==
+Administrators can disable and t...

Read more...

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

У пет, 11. 12 2009. у 11:40 +0000, Adi Roiban пише:
> Many thanks for the review... there we a couple of stupid errors :(
> For the future I will try to read the whole diff at least twice.

Great work, thanks again!

 review approve
 merge approve

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/icing/style-3-0.css'
2--- lib/canonical/launchpad/icing/style-3-0.css 2009-12-07 13:53:47 +0000
3+++ lib/canonical/launchpad/icing/style-3-0.css 2009-12-11 11:37:16 +0000
4@@ -694,6 +694,19 @@
5 padding: 2px 1em 2px 2px;
6 }
7
8+/* Templates listing.
9+ *
10+ * Examples:
11+ * https://translations.launchpad.dev/ubuntu/hoary/+templates
12+ * https://translations.launchpad.dev/evolution/trunk/+templates
13+ */
14+
15+.inactive-template td {
16+ background-color: #fee;
17+ color: #855;
18+}
19+
20+
21 /* Translations statistics and legend.
22 *
23 * Examples:
24
25=== modified file 'lib/canonical/launchpad/security.py'
26--- lib/canonical/launchpad/security.py 2009-11-19 15:14:53 +0000
27+++ lib/canonical/launchpad/security.py 2009-12-11 11:37:16 +0000
28@@ -479,6 +479,7 @@
29 """Allow Launchpad's admins and Rosetta experts edit all fields."""
30 return is_admin_or_rosetta_expert(user)
31
32+
33 class AdminProductTranslations(AuthorizationBase):
34 permission = 'launchpad.TranslationsAdmin'
35 usedfor = IProduct
36@@ -1103,27 +1104,63 @@
37 usedfor = ICodeImportMachine
38
39
40+class AdminDistributionTranslations(OnlyRosettaExpertsAndAdmins,
41+ EditDistributionByDistroOwnersOrAdmins):
42+ """Class for deciding who can administer distribution translations.
43+
44+ This class is used for `launchpad.TranslationsAdmin` privilege on
45+ `IDistribution` and `IDistroSeries` and corresponding `IPOTemplate`s,
46+ and limits access to Rosetta experts, Launchpad admins and distribution
47+ translation group owner.
48+ """
49+ permission = 'launchpad.TranslationsAdmin'
50+ usedfor = IDistribution
51+
52+ def checkAuthenticated(self, user):
53+ """Is the user able to manage `IDistribution` translations settings?
54+
55+ Any Launchpad/Launchpad Translations administrator or people allowed
56+ to edit distribution details are able to change translation settings
57+ for a distribution.
58+ """
59+ # Translation group owner for a distribution is also a
60+ # translations administrator for it.
61+ translation_group = self.obj.translationgroup
62+ if translation_group and user.inTeam(translation_group.owner):
63+ return True
64+ else:
65+ return (
66+ OnlyRosettaExpertsAndAdmins.checkAuthenticated(self, user) or
67+ EditDistributionByDistroOwnersOrAdmins.checkAuthenticated(
68+ self, user))
69+
70+
71+# Please keep AdminPOTemplateSubset in sync with this, unless you
72+# know exactly what you are doing.
73 class AdminPOTemplateDetails(OnlyRosettaExpertsAndAdmins):
74- permission = 'launchpad.Admin'
75+ """Controls administration of an `IPOTemplate`.
76+
77+ Allow all persons that can also administer the translations to
78+ which this template belongs to and also translation group owners.
79+
80+ Product owners does not have administrative privileges.
81+ """
82+ permission = 'launchpad.TranslationsAdmin'
83 usedfor = IPOTemplate
84
85 def checkAuthenticated(self, user):
86- """Allow LP/Translations admins, and for distros, owners and
87- translation group owners.
88- """
89- if OnlyRosettaExpertsAndAdmins.checkAuthenticated(self, user):
90- return True
91-
92 template = self.obj
93 if template.distroseries is not None:
94- distro = template.distroseries.distribution
95- if user.inTeam(distro.owner):
96- return True
97- translation_group = distro.translationgroup
98- if translation_group and user.inTeam(translation_group.owner):
99- return True
100+ # Template is on a distribution.
101+ distribution = template.distroseries.distribution
102+ return (
103+ AdminDistributionTranslations(
104+ distribution).checkAuthenticated(user))
105
106- return False
107+ else:
108+ # Template is on a product.
109+ return OnlyRosettaExpertsAndAdmins.checkAuthenticated(
110+ self, user)
111
112
113 class EditPOTemplateDetails(AdminPOTemplateDetails, EditByOwnersOrAdmins):
114@@ -1607,10 +1644,32 @@
115 user.inTeam(celebs.bazaar_experts))
116
117
118+# Please keep this in sync with AdminPOTemplateDetails. Note that
119+# this permission controls access to browsing into individual
120+# potemplates, but it's on a different object (POTemplateSubset)
121+# from AdminPOTemplateDetails, even though it looks almost identical
122 class AdminPOTemplateSubset(OnlyRosettaExpertsAndAdmins):
123- permission = 'launchpad.Admin'
124+ """Controls administration of an `IPOTemplateSubset`.
125+
126+ Allow all persons that can also administer the translations to
127+ which this template belongs to and also translation group owners.
128+
129+ Product owners does not have administrative privileges.
130+ """
131+ permission = 'launchpad.TranslationsAdmin'
132 usedfor = IPOTemplateSubset
133
134+ def checkAuthenticated(self, user):
135+ template_set = self.obj
136+ if template_set.distroseries is not None:
137+ distribution = template_set.distroseries.distribution
138+ return (
139+ AdminDistributionTranslations(
140+ distribution).checkAuthenticated(user))
141+ else:
142+ # Template is on a product.
143+ return OnlyRosettaExpertsAndAdmins.checkAuthenticated(self, user)
144+
145
146 class AdminDistroSeriesLanguage(OnlyRosettaExpertsAndAdmins):
147 permission = 'launchpad.Admin'
148@@ -1791,24 +1850,6 @@
149 user.inTeam(self.obj.distribution.language_pack_admin))
150
151
152-class AdminDistributionTranslations(OnlyRosettaExpertsAndAdmins,
153- EditDistributionByDistroOwnersOrAdmins):
154- permission = 'launchpad.TranslationsAdmin'
155- usedfor = IDistribution
156-
157- def checkAuthenticated(self, user):
158- """Is the user able to manage `IDistribution` translations settings?
159-
160- Any Launchpad/Launchpad Translations administrator or people allowed
161- to edit distribution details are able to change translation settings
162- for a distribution.
163- """
164- return (
165- OnlyRosettaExpertsAndAdmins.checkAuthenticated(self, user) or
166- EditDistributionByDistroOwnersOrAdmins.checkAuthenticated(
167- self, user))
168-
169-
170 class AdminLanguagePack(OnlyRosettaExpertsAndAdmins):
171 permission = 'launchpad.LanguagePacksAdmin'
172 usedfor = ILanguagePack
173
174=== modified file 'lib/lp/testing/factory.py'
175--- lib/lp/testing/factory.py 2009-12-10 20:23:49 +0000
176+++ lib/lp/testing/factory.py 2009-12-11 11:37:16 +0000
177@@ -76,6 +76,7 @@
178 from lp.translations.interfaces.translationgroup import (
179 ITranslationGroupSet)
180 from lp.translations.interfaces.translator import ITranslatorSet
181+from lp.translations.interfaces.translationsperson import ITranslationsPerson
182 from canonical.launchpad.ftests._sqlobject import syncUpdate
183 from lp.services.mail.signedmessage import SignedMessage
184 from lp.services.worlddata.interfaces.country import ICountrySet
185@@ -502,13 +503,15 @@
186 return getUtility(ITranslationGroupSet).new(
187 name, title, summary, url, owner)
188
189- def makeTranslator(self, language_code, group=None, person=None):
190+ def makeTranslator(
191+ self, language_code, group=None, person=None, license=True):
192 """Create a new, arbitrary `Translator`."""
193 language = getUtility(ILanguageSet).getLanguageByCode(language_code)
194 if group is None:
195 group = self.makeTranslationGroup()
196 if person is None:
197 person = self.makePerson()
198+ ITranslationsPerson(person).translations_relicensing_agreement = license
199 return getUtility(ITranslatorSet).new(group, language, person)
200
201 def makeMilestone(
202
203=== modified file 'lib/lp/translations/browser/configure.zcml'
204--- lib/lp/translations/browser/configure.zcml 2009-12-07 18:42:21 +0000
205+++ lib/lp/translations/browser/configure.zcml 2009-12-11 11:37:16 +0000
206@@ -420,7 +420,7 @@
207 name="+admin"
208 for="lp.translations.interfaces.potemplate.IPOTemplate"
209 class="lp.translations.browser.potemplate.POTemplateAdminView"
210- permission="launchpad.Admin"
211+ permission="launchpad.TranslationsAdmin"
212 template="../../app/templates/generic-edit.pt"
213 layer="canonical.launchpad.layers.TranslationsLayer"/>
214 <browser:page
215
216=== modified file 'lib/lp/translations/browser/potemplate.py'
217--- lib/lp/translations/browser/potemplate.py 2009-11-27 12:50:16 +0000
218+++ lib/lp/translations/browser/potemplate.py 2009-12-11 11:37:16 +0000
219@@ -184,7 +184,7 @@
220 text = 'Settings'
221 return Link('+edit', text)
222
223- @enabled_with_permission('launchpad.Admin')
224+ @enabled_with_permission('launchpad.TranslationsAdmin')
225 def administer(self):
226 text = 'Administer'
227 return Link('+admin', text)
228@@ -688,7 +688,7 @@
229 raise AssertionError('Unknown context for %s' % potemplate.title)
230
231 if ((official_rosetta and potemplate.iscurrent) or
232- check_permission('launchpad.Admin', self.context)):
233+ check_permission('launchpad.TranslationsAdmin', self.context)):
234 # The target is using officially Launchpad Translations and the
235 # template is available to be translated, or the user is a is a
236 # Launchpad administrator in which case we show everything.
237
238=== modified file 'lib/lp/translations/configure.zcml'
239--- lib/lp/translations/configure.zcml 2009-09-26 10:03:31 +0000
240+++ lib/lp/translations/configure.zcml 2009-12-11 11:37:16 +0000
241@@ -411,7 +411,7 @@
242 permission="launchpad.Edit"
243 set_attributes="owner priority description"/>
244 <require
245- permission="launchpad.Admin"
246+ permission="launchpad.TranslationsAdmin"
247 set_attributes="name translation_domain productseries distroseries sourcepackagename sourcepackageversion binarypackagename languagepack path header iscurrent from_sourcepackagename date_last_updated source_file source_file_format"/>
248 </class>
249 <adapter
250
251=== modified file 'lib/lp/translations/stories/distroseries/xx-distroseries-templates.txt'
252--- lib/lp/translations/stories/distroseries/xx-distroseries-templates.txt 2009-11-30 18:18:08 +0000
253+++ lib/lp/translations/stories/distroseries/xx-distroseries-templates.txt 2009-12-11 11:37:16 +0000
254@@ -1,5 +1,3 @@
255-
256-
257 Templates view for DistroSeries
258 ===============================
259
260@@ -20,6 +18,7 @@
261 >>> print user_browser.url
262 http://translations.launchpad.dev/ubuntu/hoary/+templates
263
264+
265 The templates table
266 -------------------
267
268@@ -35,8 +34,7 @@
269
270 >>> table = find_tag_by_id(anon_browser.contents, 'templates_table')
271 >>> print extract_text(table)
272- Source package Template name Last update
273- evolution disabled-template 2007-01-05
274+ Source package Template name Last update
275 evolution evolution-2.2 2005-05-06
276 evolution man 2006-08-14
277 mozilla pkgconf-mozilla 2005-05-06
278@@ -44,18 +42,19 @@
279 pmount pmount 2005-05-06
280
281
282-Logged-in users will see a link from distro series
283+Logged-in users see a link to all the active translation templates
284+on a distribution series translation page.
285 >>> user_browser.open(
286 ... 'http://translations.launchpad.dev/ubuntu/hoary')
287 >>> user_browser.getLink('full list of templates').click()
288
289-Logged-in users can also choose to download all translations for each
290-of the templates.
291+Regular users only see the option to download translations for each of
292+the active templates.
293
294 >>> table = find_tag_by_id(user_browser.contents, 'templates_table')
295 >>> print extract_text(table)
296 Source package Template name Last update Actions
297- evolution disabled-template 2007-01-05 Download
298+ evolution evolution-2.2 2005-05-06 Download
299 ...
300 mozilla pkgconf-mozilla 2005-05-06 Download
301 ...
302@@ -70,13 +69,70 @@
303
304 >>> table = find_tag_by_id(admin_browser.contents, 'templates_table')
305 >>> print extract_text(table)
306- Source package Template name Last update Actions
307- evolution disabled-template 2007-01-05 Edit Upload Download Administer
308- evolution evolution-2.2 2005-05-06 Edit Upload Download Administer
309- evolution man 2006-08-14 Edit Upload Download Administer
310- mozilla pkgconf-mozilla 2005-05-06 Edit Upload Download Administer
311- pmount man 2006-08-14 Edit Upload Download Administer
312- pmount pmount 2005-05-06 Edit Upload Download Administer
313+ Source package Template name Last update Actions
314+ evolution disabled-template (inactive) 2007-01-05 Edit Upload Download Administer
315+ evolution evolution-2.2 2005-05-06 Edit Upload Download Administer
316+ evolution man 2006-08-14 Edit Upload Download Administer
317+ mozilla pkgconf-mozilla 2005-05-06 Edit Upload Download Administer
318+ pmount man 2006-08-14 Edit Upload Download Administer
319+ pmount pmount 2005-05-06 Edit Upload Download Administer
320+
321+Ubuntu Translations Coordinator can administer all templates, including
322+those that are currently disabled.
323+
324+New user is made an owner of an Ubuntu translation group, thus being
325+considered a translations administrator for Ubuntu.
326+
327+ >>> from zope.component import getUtility
328+ >>> from canonical.launchpad.ftests import login, logout
329+ >>> from canonical.launchpad.interfaces import (
330+ ... IDistributionSet, IPersonSet, ILaunchpadCelebrities)
331+ >>> login('foo.bar@canonical.com')
332+ >>> a_person = factory.makePerson(email='utc-member@example.com',
333+ ... name='utc-member', password='test', displayname='Some Guy')
334+ >>> utc_member = factory.makeTranslator('ro', person=a_person)
335+ >>> utc_team = factory.makeTeam(owner=a_person)
336+ >>> utg = factory.makeTranslationGroup(owner=utc_team,
337+ ... name="Ubuntu Translation Group")
338+ >>> ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
339+ >>> ubuntu.translationgroup = utg
340+ >>> # Log out so we can go back to using test browsers.
341+ >>> logout()
342+
343+Visiting the Ubuntu Hoary templates page, translation administrators
344+will see "Edit" and "Administer" links for all templates.
345+
346+Trying to edit/administer enabled templates brings them to the
347+appropriate page.
348+
349+ >>> utc_browser = setupBrowser(auth='Basic utc-member@example.com:test')
350+ >>> utc_browser.open(
351+ ... 'http://translations.launchpad.dev/ubuntu/hoary/+templates')
352+ >>> utc_browser.getLink(url='ubuntu/hoary/+source/evolution/+pots/evolution-2.2/+edit').click()
353+ >>> print utc_browser.url
354+ http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/+edit
355+
356+ >>> utc_browser.open(
357+ ... 'http://translations.launchpad.dev/ubuntu/hoary/+templates')
358+ >>> utc_browser.getLink(url='/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/+admin').click()
359+ >>> print utc_browser.url
360+ http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/+admin
361+
362+Trying to edit/administer disabled templates brings them to the
363+appropriate page.
364+
365+ >>> utc_browser = setupBrowser(auth='Basic utc-member@example.com:test')
366+ >>> utc_browser.open(
367+ ... 'http://translations.launchpad.dev/ubuntu/hoary/+templates')
368+ >>> utc_browser.getLink(url='ubuntu/hoary/+source/evolution/+pots/disabled-template/+edit').click()
369+ >>> print utc_browser.url
370+ http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/disabled-template/+edit
371+
372+ >>> utc_browser.open(
373+ ... 'http://translations.launchpad.dev/ubuntu/hoary/+templates')
374+ >>> utc_browser.getLink(url='/ubuntu/hoary/+source/evolution/+pots/disabled-template/+admin').click()
375+ >>> print utc_browser.url
376+ http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/disabled-template/+admin
377
378
379 Links to the templates
380@@ -97,3 +153,4 @@
381 >>> admin_browser.getLink('Edit').click()
382 >>> print admin_browser.url
383 http://translations.../evolution/+pots/disabled-template/+edit
384+
385
386=== renamed file 'lib/lp/translations/stories/standalone/xx-rosetta-pofile-export.txt' => 'lib/lp/translations/stories/standalone/xx-pofile-export.txt'
387=== modified file 'lib/lp/translations/stories/standalone/xx-potemplate-admin.txt'
388--- lib/lp/translations/stories/standalone/xx-potemplate-admin.txt 2009-08-12 05:09:36 +0000
389+++ lib/lp/translations/stories/standalone/xx-potemplate-admin.txt 2009-12-11 11:37:16 +0000
390@@ -1,3 +1,10 @@
391+Administering POTemplates
392+=========================
393+
394+
395+Product templates
396+-----------------
397+
398 The POTemplate admin page lets us to edit any aspect of that object, that's
399 why we need to be a Rosetta Expert or a Launchpad admin to use it.
400
401@@ -155,8 +162,29 @@
402 >>> print admin_browser.url
403 http://translations.launchpad.dev/evolution/trunk/+pots/evolution-renamed
404
405-
406-== Distribution templates ==
407+Administrators can disable and then make changes to a disabled template.
408+
409+ >>> admin_browser.open(
410+ ... 'http://translations.launchpad.dev/evolution/trunk/+pots/'
411+ ... 'evolution-renamed/+admin')
412+ >>> admin_browser.getControl(name='field.iscurrent').value = False
413+ >>> admin_browser.getControl('Change').click()
414+ >>> print admin_browser.url
415+ http://translations.launchpad.dev/evolution/trunk/+pots/evolution-renamed
416+
417+Now we will reenable the template.
418+
419+ >>> admin_browser.open(
420+ ... 'http://translations.launchpad.dev/evolution/trunk/+pots/'
421+ ... 'evolution-renamed/+admin')
422+ >>> admin_browser.getControl(name='field.iscurrent').value = True
423+ >>> admin_browser.getControl('Change').click()
424+ >>> print admin_browser.url
425+ http://translations.launchpad.dev/evolution/trunk/+pots/evolution-renamed
426+
427+
428+Distribution templates
429+----------------------
430
431 Distributions get slightly wider permissions to manage their templates
432 autonomously.
433@@ -214,3 +242,15 @@
434
435 >>> print template.path
436 splat.pot
437+
438+Distribution translation coordinators can disable and manage disabled
439+templates.
440+
441+ >>> group_owner_browser.open(template_admin_url)
442+ >>> group_owner_browser.getControl(name='field.iscurrent').value = False
443+ >>> group_owner_browser.getControl('Change').click()
444+ >>> group_owner_browser.open(template_admin_url)
445+ >>> group_owner_browser.getControl(name='field.iscurrent').value = True
446+ >>> group_owner_browser.getControl('Change').click()
447+
448+
449
450=== renamed file 'lib/lp/translations/stories/standalone/xx-rosetta-potemplate-export.txt' => 'lib/lp/translations/stories/standalone/xx-potemplate-export.txt'
451=== renamed file 'lib/lp/translations/stories/standalone/xx-rosetta-potemplate-index.txt' => 'lib/lp/translations/stories/standalone/xx-potemplate-index.txt'
452--- lib/lp/translations/stories/standalone/xx-rosetta-potemplate-index.txt 2009-11-09 17:08:21 +0000
453+++ lib/lp/translations/stories/standalone/xx-potemplate-index.txt 2009-12-11 11:37:16 +0000
454@@ -1,4 +1,9 @@
455-= POTemplate index page =
456+POTemplate index page
457+=====================
458+
459+
460+DistoSeries
461+-----------
462
463 The index page for a POTemplate lists all available translations
464 for a source package. No Privileges Person visits the
465@@ -74,6 +79,10 @@
466 Dutch ... ... ... ... Luk Claes
467 Finnish ... ... ... ... P\xf6ll\xe4
468
469+
470+DistroSeries and ProductSeries links to related templates
471+---------------------------------------------------------
472+
473 We are presented not only with links to alternate templates from the same
474 source, but also with links to the same template in (other) distroseries.
475
476@@ -95,3 +104,63 @@
477 >>> print alternate_notice
478 <p...<a href="/evolution/trunk/+pots/evolution-2.2"...
479
480+
481+Administering templates
482+-----------------------
483+
484+Anonymous visitors see only a list of all existing templates, with no
485+administration or download/upload links.
486+
487+ >>> anon_browser.open('http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2')
488+ >>> anon_browser.getLink('upload')
489+ Traceback (most recent call last):
490+ ...
491+ LinkNotFoundError
492+
493+ >>> anon_browser.getLink('download').click()
494+ Traceback (most recent call last):
495+ ...
496+ LinkNotFoundError
497+
498+As an authenticated user, you should see the download link,
499+but not the one for uploading file to this potemplate.
500+
501+ >>> user_browser.open('http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2')
502+ >>> user_browser.getLink('upload')
503+ Traceback (most recent call last):
504+ ...
505+ LinkNotFoundError
506+
507+ >>> user_browser.getLink('download').click()
508+ >>> print user_browser.url
509+ http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/+export
510+
511+Translation administrators will see both download and upload links.
512+Beside administering this template, "Change permissions"
513+and "Change details" should be also accessible.
514+
515+ >>> admin_browser.open('http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2')
516+ >>> admin_browser.getLink('upload').click()
517+ >>> print admin_browser.url
518+ http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/+upload
519+
520+ >>> admin_browser.open('http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2')
521+ >>> admin_browser.getLink('download').click()
522+ >>> print admin_browser.url
523+ http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/+export
524+
525+ >>> admin_browser.open('http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2')
526+ >>> admin_browser.getLink('Administer this template').click()
527+ >>> print admin_browser.url
528+ http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/+admin
529+
530+ >>> admin_browser.open('http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2')
531+ >>> admin_browser.getLink('Change details').click()
532+ >>> print admin_browser.url
533+ http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/+edit
534+
535+ >>> admin_browser.open('http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2')
536+ >>> admin_browser.getLink('Change permissions').click()
537+ >>> print admin_browser.url
538+ http://translations.launchpad.dev/ubuntu/+settings
539+
540
541=== modified file 'lib/lp/translations/templates/object-templates.pt'
542--- lib/lp/translations/templates/object-templates.pt 2009-12-07 14:19:02 +0000
543+++ lib/lp/translations/templates/object-templates.pt 2009-12-11 11:37:16 +0000
544@@ -82,51 +82,107 @@
545 </tr>
546 </thead>
547 <tbody>
548- <tr tal:repeat="template view/iter_templates" class="template_row">
549- <td tal:condition="view/is_distroseries"
550- tal:content="template/sourcepackagename/name"
551- class="sourcepackage_column">Source package
552- </td>
553- <td class="template_column"><a tal:attributes="href template/fmt:url"
554- tal:content="template/name">Template name</a></td>
555- <td class="lastupdate_column">
556- <span class="sortkey"
557- tal:condition="template/date_last_updated"
558- tal:content="template/date_last_updated/fmt:datetime">
559- time sort key
560- </span>
561- <span class="lastupdate_column"
562- tal:condition="template/date_last_updated"
563- tal:attributes="
564- title template/date_last_updated/fmt:datetime"
565- tal:content="
566- template/date_last_updated/fmt:approximatedate"
567- >
568- 2009-09-23
569- </span>
570- </td>
571- <td class="actions_column"
572- tal:condition="context/required:launchpad.AnyPerson">
573- <div class="template_links">
574- <tal:maintainer condition="template/required:launchpad.Edit">
575- <a tal:attributes="href string:${template/fmt:url}/+edit;
576- title string:Edit ${template/name}'s details">
577- <img src="/@@/edit" />&nbsp;Edit</a>
578- <a tal:attributes="href string:${template/fmt:url}/+upload;
579- title string:Upload translations to ${template/name}">
580- <img src="/@@/add" />&nbsp;Upload</a>
581- </tal:maintainer>
582- <a tal:attributes="href string:${template/fmt:url}/+export;
583- title string:Download translations from ${template/name}">
584- <img src="/@@/download" />&nbsp;Download</a>
585- <tal:admin condition="template/required:launchpad.Admin">
586- <a tal:attributes="href string:${template/fmt:url}/+admin;
587- title string:Administer ${template/name}">
588- <img src="/@@/edit" />&nbsp;Administer</a>
589+ <tal:templates repeat="template view/iter_templates">
590+ <tal:not-current condition="not: template/iscurrent">
591+ <tal:admin condition="template/required:launchpad.TranslationsAdmin">
592+ <tr class="template_row inactive-template">
593+ <td tal:condition="view/is_distroseries"
594+ tal:content="template/sourcepackagename/name"
595+ class="sourcepackage_column">Source package
596+ </td>
597+ <td class="template_column">
598+ <a tal:attributes="href template/fmt:url">
599+ <span tal:content="template/name">Template name</span>
600+ </a> (inactive)
601+ </td>
602+ <td class="lastupdate_column">
603+ <span class="sortkey"
604+ tal:condition="template/date_last_updated"
605+ tal:content="template/date_last_updated/fmt:datetime">
606+ time sort key
607+ </span>
608+ <span class="lastupdate_column"
609+ tal:condition="template/date_last_updated"
610+ tal:attributes="
611+ title template/date_last_updated/fmt:datetime"
612+ tal:content="
613+ template/date_last_updated/fmt:approximatedate"
614+ >
615+ 2009-09-23
616+ </span>
617+ </td>
618+ <td class="actions_column"
619+ tal:condition="context/required:launchpad.AnyPerson">
620+ <div class="template_links">
621+ <tal:maintainer condition="template/required:launchpad.Edit">
622+ <a tal:attributes="href string:${template/fmt:url}/+edit;
623+ title string:Edit ${template/name}'s details">
624+ <img src="/@@/edit" />&nbsp;Edit</a>
625+ <a tal:attributes="href string:${template/fmt:url}/+upload;
626+ title string:Upload translations to ${template/name}">
627+ <img src="/@@/add" />&nbsp;Upload</a>
628+ </tal:maintainer>
629+ <a tal:attributes="href string:${template/fmt:url}/+export;
630+ title string:Download translations from ${template/name}">
631+ <img src="/@@/download" />&nbsp;Download</a>
632+ <tal:admin condition="template/required:launchpad.TranslationsAdmin">
633+ <a tal:attributes="href string:${template/fmt:url}/+admin;
634+ title string:Administer ${template/name}">
635+ <img src="/@@/edit" />&nbsp;Administer</a>
636+ </tal:admin>
637+ </div>
638+ </td>
639+ </tr>
640 </tal:admin>
641- </div>
642- </td>
643- </tr>
644+ </tal:not-current>
645+ <tal:current condition="template/iscurrent">
646+ <tr class="template_row">
647+ <td tal:condition="view/is_distroseries"
648+ tal:content="template/sourcepackagename/name"
649+ class="sourcepackage_column">Source package
650+ </td>
651+ <td class="template_column"><a tal:attributes="href template/fmt:url"
652+ tal:content="template/name">Template name</a></td>
653+ <td class="lastupdate_column">
654+ <span class="sortkey"
655+ tal:condition="template/date_last_updated"
656+ tal:content="template/date_last_updated/fmt:datetime">
657+ time sort key
658+ </span>
659+ <span class="lastupdate_column"
660+ tal:condition="template/date_last_updated"
661+ tal:attributes="
662+ title template/date_last_updated/fmt:datetime"
663+ tal:content="
664+ template/date_last_updated/fmt:approximatedate"
665+ >
666+ 2009-09-23
667+ </span>
668+ </td>
669+ <td class="actions_column"
670+ tal:condition="context/required:launchpad.AnyPerson">
671+ <div class="template_links">
672+ <tal:maintainer condition="template/required:launchpad.Edit">
673+ <a tal:attributes="href string:${template/fmt:url}/+edit;
674+ title string:Edit ${template/name}'s details">
675+ <img src="/@@/edit" />&nbsp;Edit</a>
676+ <a tal:attributes="href string:${template/fmt:url}/+upload;
677+ title string:Upload translations to ${template/name}">
678+ <img src="/@@/add" />&nbsp;Upload</a>
679+ </tal:maintainer>
680+ <a tal:attributes="href string:${template/fmt:url}/+export;
681+ title string:Download translations from ${template/name}">
682+ <img src="/@@/download" />&nbsp;Download</a>
683+ <tal:admin condition="template/required:launchpad.TranslationsAdmin">
684+ <a tal:attributes="href string:${template/fmt:url}/+admin;
685+ title string:Administer ${template/name}">
686+ <img src="/@@/edit" />&nbsp;Administer</a>
687+ </tal:admin>
688+ </div>
689+ </td>
690+ </tr>
691+ </tal:current>
692+ </tal:templates>
693 </tbody>
694 </table>
695 </div>
696
697=== modified file 'lib/lp/translations/templates/potemplate-index.pt'
698--- lib/lp/translations/templates/potemplate-index.pt 2009-10-15 14:04:48 +0000
699+++ lib/lp/translations/templates/potemplate-index.pt 2009-12-11 11:37:16 +0000
700@@ -115,7 +115,7 @@
701 class="download sprite">download</a>
702 translation tarballs.
703 </p>
704- <div tal:condition="context/required:launchpad.Admin">
705+ <div tal:condition="context/required:launchpad.TranslationsAdmin">
706 <a tal:attributes="href
707 context/menu:navigation/administer/url"
708 class="edit sprite">