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

Proposed by Adi Roiban
Status: Merged
Approved by: Jeroen T. Vermeulen
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~adiroiban/launchpad/bug-127171
Merge into: lp:launchpad
Diff against target: 802 lines (+226/-88)
18 files modified
lib/canonical/launchpad/security.py (+23/-9)
lib/canonical/launchpad/testing/pages.py (+37/-4)
lib/lp/registry/browser/product.py (+4/-2)
lib/lp/registry/configure.zcml (+23/-11)
lib/lp/registry/interfaces/distribution.py (+2/-2)
lib/lp/registry/interfaces/product.py (+5/-5)
lib/lp/registry/interfaces/project.py (+5/-4)
lib/lp/translations/browser/configure.zcml (+8/-8)
lib/lp/translations/browser/distribution.py (+3/-5)
lib/lp/translations/browser/potemplate.py (+1/-1)
lib/lp/translations/browser/product.py (+6/-6)
lib/lp/translations/browser/project.py (+5/-5)
lib/lp/translations/browser/translationgroup.py (+3/-4)
lib/lp/translations/interfaces/translationgroup.py (+2/-2)
lib/lp/translations/stories/translationgroups/46-test-distro-structured-permissions.txt (+1/-1)
lib/lp/translations/stories/translationgroups/xx-change-translation-policy.txt (+92/-12)
lib/lp/translations/templates/distribution-language-pack-admin-info.pt (+2/-2)
lib/lp/translations/templates/hastranslationgroup-portlet-translation-groups-and-permission.pt (+4/-5)
To merge this branch: bzr merge lp:~adiroiban/launchpad/bug-127171
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) code Approve
Review via email: mp+19444@code.launchpad.net

Commit message

+changetranslators becomes +settings, and rosetta admins can access it.

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

= Bug 127171 =

A Translations Expert (or Rosetta Admin, or the other way around) cannot access the "Change translators" page.

For example, if he/she goes to: https://translations.launchpad.net/pmount/+translations, the Change translators option is shown, but clicking it result in:

  Not allowed here
  Sorry, you don't have permission to access this page.
  You are logged in as [...]

== Proposed fix ==

Allow Rosetta Admins to edit translation options for a project, a product and a distribution.

== Pre-implementation notes ==

After the fix for bug #422056 landed on edge, the Change translator page has been generalized and it does not include only option to change translations.
Talking with Danilo we decided to rename this page to +settings (see bug 516317).

Talking with Danilo, and as noted in the bug comments, Rosetta Experts should have access to both Product and Distribution settings. Translation group owners should not be able to edit project translation settings.

While Ubuntu is the only distribution using Launchpad Translations, the owners of a translation group will have access only to distributions (to help Ubuntu Translations Coordinators) not to projects using that translation group.

Talking with Jeroen, we decided to rename IHasTranslationGroup to ITranslationPolicy as it makes more sense.

== Implementation details ==
Since we are going to add more pagetests for Rosetta Experts, I have added setupRosettaExpertBrowser() to simplify such tests. I have also improved the previous code for creating a translation group owner browser.

The launchpad.TranslationAdmin permission was added for IProduct, IProject and IDistribution allowing Launchpad Admin, Rosetta Admins and owners to edit translation related attributes.

I did not touch the code popping in the lint warnings. Any advice for fixing them is much appreciated.

== Tests ==
lp-test -t permission -t policy

== Demo and Q/A ==
Log in as Rosetta admin (ex <email address hidden>).

Go to a product or project translations page:
ie. https://translations.launchpad.dev/evolution
https://translations.launchpad.dev/gnome

You should see the „Change permissions” link, be able to access the page and change the values from that page.

Same story for a distribution page:
https://translations.launchpad.dev/ubuntu

Login as a normal user you should not be able to see the link of edit the 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/canonical/launchpad/testing/pages.py
  lib/lp/registry/configure.zcml
  lib/lp/registry/browser/product.py
  lib/lp/registry/interfaces/distribution.py
  lib/lp/registry/interfaces/product.py
  lib/lp/registry/interfaces/project.py
  lib/lp/translations/browser/configure.zcml
  lib/lp/translations/browser/distribution.py
  lib/lp/translations/browser/potemplate.py
  lib/lp/translations/browser/product.py
  lib/lp/translations/browser/project.py
  lib/lp/translations/browser/translationgroup.py
  lib/lp/translations/interfaces/translationgroup.py
  lib/lp/translations/stories/translation...

Read more...

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Fantastic branch and cover letter. Thanks for all the drive-by cleanups!

Don't worry about those lint reports; pylint gets confused sometimes.

I only have two very minor notes about the changes:

Line 410: I believe our policies nowadays for function, method, and class definitions with parameter lists that don't fit the line is to indent the next line to just behind the opening parenthesis. I hope the spacing comes across here, but:

{{{
409 +class IProject(IProjectPublic, IStructuralSubscriptionTarget,
410 + ITranslationPolicy):
}}}

...should be:

{{{
409 +class IProject(IProjectPublic, IStructuralSubscriptionTarget,
410 + ITranslationPolicy):
}}}

That is, ITranslationPolicy should be directly under IProjectPublic.

Line 551: When we wrap lists (of multiple items) to multiple lines, we keep the closing bracket on a line of its own. The last element in the list should have a trailing comma. So:

{{{
546 field_names = [
547 "translationgroup",
548 "translationpermission",
549 - "translation_focus"
550 - ]
551 + "translation_focus"]
}}}

...was actually correct apart from the missing comma. It should be:

{{{
546 field_names = [
547 "translationgroup",
548 "translationpermission",
549 - "translation_focus"
549 + "translation_focus",
550 ]
}}}

Very minor comments indeed, but I could think of nothing else. :-)

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

În data de Mi, 17-02-2010 la 09:18 +0000, Jeroen T. Vermeulen a scris:
> Review: Approve code
> Fantastic branch and cover letter. Thanks for all the drive-by cleanups!
>
> Don't worry about those lint reports; pylint gets confused sometimes.
>
> I only have two very minor notes about the changes:

I have fixed and pushed the formating problems.

Can you please put this branch on queue for ec2 testings?

Many thanks!

--
Adi Roiban

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/security.py'
2--- lib/canonical/launchpad/security.py 2010-02-10 23:14:56 +0000
3+++ lib/canonical/launchpad/security.py 2010-02-17 09:39:19 +0000
4@@ -496,6 +496,21 @@
5 return user.in_admin or user.in_rosetta_experts
6
7
8+class AdminProjectTranslations(AuthorizationBase):
9+ permission = 'launchpad.TranslationsAdmin'
10+ usedfor = IProject
11+
12+ def checkAuthenticated(self, user):
13+ """Is the user able to manage `IProject` translations settings?
14+
15+ Any Launchpad/Launchpad Translations administrator or owner is
16+ able to change translation settings for a project.
17+ """
18+ return (user.isOwner(self.obj) or
19+ user.in_rosetta_experts or
20+ user.in_admin)
21+
22+
23 class AdminProductTranslations(AuthorizationBase):
24 permission = 'launchpad.TranslationsAdmin'
25 usedfor = IProduct
26@@ -1100,8 +1115,7 @@
27 usedfor = ICodeImportMachine
28
29
30-class AdminDistributionTranslations(OnlyRosettaExpertsAndAdmins,
31- EditDistributionByDistroOwnersOrAdmins):
32+class AdminDistributionTranslations(AuthorizationBase):
33 """Class for deciding who can administer distribution translations.
34
35 This class is used for `launchpad.TranslationsAdmin` privilege on
36@@ -1115,9 +1129,9 @@
37 def checkAuthenticated(self, user):
38 """Is the user able to manage `IDistribution` translations settings?
39
40- Any Launchpad/Launchpad Translations administrator or people allowed
41- to edit distribution details are able to change translation settings
42- for a distribution.
43+ Any Launchpad/Launchpad Translations administrator, translation group
44+ owner or a person allowed to edit distribution details is able to
45+ change translations settings for a distribution.
46 """
47 # Translation group owner for a distribution is also a
48 # translations administrator for it.
49@@ -1125,10 +1139,9 @@
50 if translation_group and user.inTeam(translation_group.owner):
51 return True
52 else:
53- return (
54- OnlyRosettaExpertsAndAdmins.checkAuthenticated(self, user) or
55- EditDistributionByDistroOwnersOrAdmins.checkAuthenticated(
56- self, user))
57+ return (user.in_rosetta_experts or
58+ EditDistributionByDistroOwnersOrAdmins(
59+ self.obj).checkAuthenticated(user))
60
61
62 class AdminPOTemplateDetails(OnlyRosettaExpertsAndAdmins):
63@@ -1319,6 +1332,7 @@
64 """
65 return self.obj.canEdit(user)
66
67+
68 class AdminTranslationImportQueue(OnlyRosettaExpertsAndAdmins):
69 permission = 'launchpad.Admin'
70 usedfor = ITranslationImportQueue
71
72=== modified file 'lib/canonical/launchpad/testing/pages.py'
73--- lib/canonical/launchpad/testing/pages.py 2010-01-04 16:02:31 +0000
74+++ lib/canonical/launchpad/testing/pages.py 2010-02-17 09:39:19 +0000
75@@ -28,9 +28,11 @@
76 from zope.component import getUtility
77 from zope.testbrowser.testing import Browser
78 from zope.testing import doctest
79+from zope.security.proxy import removeSecurityProxy
80
81 from canonical.launchpad.interfaces import (
82- IOAuthConsumerSet, OAUTH_REALM, ILaunchpadCelebrities)
83+ IOAuthConsumerSet, OAUTH_REALM, ILaunchpadCelebrities,
84+ TeamMembershipStatus)
85 from canonical.launchpad.testing.systemdocs import (
86 LayeredDocFileSuite, SpecialOutputChecker, strip_prefix)
87 from canonical.launchpad.webapp import canonical_url
88@@ -51,6 +53,7 @@
89 sending both Basic Auth and cookie credentials raises an exception
90 (Bug 39881).
91 """
92+
93 def __init__(self, *args, **kw):
94 if kw.get('debug'):
95 self._debug = True
96@@ -203,7 +206,9 @@
97
98 def find_tags_by_class(content, class_, only_first=False):
99 """Find and return one or more tags matching the given class(es)"""
100+
101 match_classes = set(class_.split())
102+
103 def class_matcher(value):
104 if value is None:
105 return False
106@@ -263,6 +268,7 @@
107 for message in get_feedback_messages(content):
108 print message
109
110+
111 def print_table(content, columns=None, skip_rows=None, sep="\t"):
112 """Given a <table> print the content of each row.
113
114@@ -283,6 +289,7 @@
115 if len(row_content) > 0:
116 print sep.join(row_content)
117
118+
119 def print_radio_button_field(content, name):
120 """Find the input called field.name, and print a friendly representation.
121
122@@ -457,7 +464,7 @@
123
124 def print_navigation_links(content):
125 """Print navigation menu urls."""
126- navigation_links = find_tag_by_id(content, 'navigation-tabs')
127+ navigation_links = find_tag_by_id(content, 'navigation-tabs')
128 if navigation_links is None:
129 print "No navigation links"
130 return
131@@ -527,7 +534,7 @@
132
133 def print_batch_header(soup):
134 """Print the batch navigator header."""
135- navigation = soup.find('td', {'class' : 'batch-navigation-index'})
136+ navigation = soup.find('td', {'class': 'batch-navigation-index'})
137 print extract_text(navigation).encode('ASCII', 'backslashreplace')
138
139
140@@ -669,18 +676,43 @@
141 login('foo.bar@canonical.com')
142 try:
143 dtg_member = LaunchpadObjectFactory().makePerson(
144+ name='ubuntu-translations-coordinator',
145 email="dtg-member@ex.com", password="test")
146 except NameAlreadyTaken:
147 # We have already created the translations coordinator
148 pass
149 else:
150- dtg = LaunchpadObjectFactory().makeTranslationGroup(owner=dtg_member)
151+ dtg = LaunchpadObjectFactory().makeTranslationGroup(
152+ name="ubuntu-translators",
153+ title="Ubuntu Translators",
154+ owner=dtg_member)
155 ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
156 ubuntu.translationgroup = dtg
157 logout()
158 return setupBrowser(auth='Basic dtg-member@ex.com:test')
159
160
161+def setupRosettaExpertBrowser():
162+ """Testbrowser configured for Rosetta Experts."""
163+
164+ login('admin@canonical.com')
165+ try:
166+ rosetta_expert = LaunchpadObjectFactory().makePerson(
167+ name='rosetta-experts-member',
168+ email='re@ex.com', password='test')
169+ except NameAlreadyTaken:
170+ # We have already created an Rosetta expert
171+ pass
172+ else:
173+ rosetta_experts_team = removeSecurityProxy(getUtility(
174+ ILaunchpadCelebrities).rosetta_experts)
175+ rosetta_experts_team.addMember(
176+ rosetta_expert, reviewer=rosetta_experts_team,
177+ status=TeamMembershipStatus.ADMIN)
178+ logout()
179+ return setupBrowser(auth='Basic re@ex.com:test')
180+
181+
182 def stop():
183 # Temporarily restore the real stdout.
184 old_stdout = sys.stdout
185@@ -704,6 +736,7 @@
186 'launchpad-library', '')
187 test.globs['setupBrowser'] = setupBrowser
188 test.globs['setupDTCBrowser'] = setupDTCBrowser
189+ test.globs['setupRosettaExpertBrowser'] = setupRosettaExpertBrowser
190 test.globs['browser'] = setupBrowser()
191 test.globs['anon_browser'] = setupBrowser()
192 test.globs['user_browser'] = setupBrowser(
193
194=== modified file 'lib/lp/registry/browser/product.py'
195--- lib/lp/registry/browser/product.py 2010-01-21 02:16:15 +0000
196+++ lib/lp/registry/browser/product.py 2010-02-17 09:39:19 +0000
197@@ -174,6 +174,7 @@
198 Requires the "product" attribute be set in the child
199 classes' action handler.
200 """
201+
202 def validate(self, data):
203 """Validate 'licenses' and 'license_info'.
204
205@@ -221,6 +222,7 @@
206 "Launchpad", config.canonical.noreply_from_address)
207 license_titles = '\n'.join(
208 license.title for license in self.product.licenses)
209+
210 def indent(text):
211 text = '\n '.join(line for line in text.split('\n'))
212 text = ' ' + text
213@@ -524,7 +526,6 @@
214 series_list.insert(0, self.product.development_focus)
215 return series_list
216
217-
218 @property
219 def sorted_series_list(self):
220 """Return a sorted list of series.
221@@ -986,6 +987,7 @@
222 Replaces the use of a (series, release) tuple so that it can be more
223 clearly addressed in the view class.
224 """
225+
226 def __init__(self, series, release):
227 self.series = series
228 self.release = release
229@@ -1197,6 +1199,7 @@
230 'for this project first.',
231 canonical_url(self.context, rootsite="bugs")))
232
233+
234 class ProductAdminView(ProductEditView, EditPrivateBugsMixin):
235 label = "Administer project details"
236 field_names = ["name", "owner", "active", "autoupdate", "private_bugs"]
237@@ -1693,7 +1696,6 @@
238 # StepView requires that its validate() method not be overridden, so make
239 # sure this calls the right method. validateStep() will call the license
240 # validation code.
241-
242 def validate(self, data):
243 """See `MultiStepView`."""
244 StepView.validate(self, data)
245
246=== modified file 'lib/lp/registry/configure.zcml'
247--- lib/lp/registry/configure.zcml 2010-02-16 20:36:48 +0000
248+++ lib/lp/registry/configure.zcml 2010-02-17 09:39:19 +0000
249@@ -273,9 +273,14 @@
250 interface="canonical.launchpad.interfaces.IFAQCollection"/>
251 <allow
252 interface="canonical.launchpad.interfaces.IQuestionCollection"/>
253+ <allow
254+ interface="lp.translations.interfaces.translationgroup.ITranslationPolicy"/>
255 <require
256 permission="launchpad.Edit"
257 set_schema="lp.registry.interfaces.project.IProjectPublic"/>
258+ <require
259+ permission="launchpad.TranslationsAdmin"
260+ set_schema="lp.translations.interfaces.translationgroup.ITranslationPolicy"/>
261
262 <!-- IStructuralSubscriptionTarget -->
263
264@@ -1081,9 +1086,7 @@
265 redeemSubscriptionVoucher releaseroot
266 remote_product screenshotsurl
267 security_contact sourceforgeproject
268- summary title translationgroup
269- translationpermission translation_focus
270- wikiurl"/>
271+ summary title wikiurl"/>
272
273 <!-- mark 2006-04-10 I put "name" in the admin group because
274 with Bazaar now in place, lots of people can have personal
275@@ -1100,6 +1103,12 @@
276 permission="launchpad.Admin"
277 set_attributes="name autoupdate registrant"/>
278 <require
279+ permission="launchpad.TranslationsAdmin"
280+ set_attributes="
281+ translation_focus
282+ translationgroup
283+ translationpermission"/>
284+ <require
285 permission="zope.Public"
286 attributes="
287 qualifies_for_free_hosting"/>
288@@ -1375,16 +1384,19 @@
289 interface="lp.registry.interfaces.distribution.IDistributionDriverRestricted"/>
290 <require
291 permission="launchpad.Edit"
292- set_attributes="displayname title summary description
293- translation_focus translationgroup translationpermission
294- driver members owner security_contact mirror_admin
295- homepage_content icon logo mugshot enable_bug_expiration
296- bug_reporting_guidelines official_blueprints
297- official_malone official_rosetta official_answers
298- official_bug_tags"/>
299+ set_attributes="
300+ displayname title summary description driver
301+ members owner security_contact mirror_admin homepage_content
302+ icon logo mugshot enable_bug_expiration
303+ bug_reporting_guidelines official_blueprints official_malone
304+ official_rosetta official_answers official_bug_tags"/>
305 <require
306 permission="launchpad.TranslationsAdmin"
307- set_attributes="language_pack_admin"/>
308+ set_attributes="
309+ language_pack_admin
310+ translationgroup
311+ translationpermission
312+ translation_focus"/>
313
314 <!-- IHasAliases -->
315
316
317=== modified file 'lib/lp/registry/interfaces/distribution.py'
318--- lib/lp/registry/interfaces/distribution.py 2010-02-04 21:18:32 +0000
319+++ lib/lp/registry/interfaces/distribution.py 2010-02-17 09:39:19 +0000
320@@ -55,7 +55,7 @@
321 ISpecificationTarget)
322 from lp.blueprints.interfaces.sprint import IHasSprints
323 from lp.translations.interfaces.translationgroup import (
324- IHasTranslationGroup)
325+ ITranslationPolicy)
326 from canonical.launchpad.webapp.interfaces import NameLookupFailed
327 from canonical.launchpad.validators.name import name_validator
328 from canonical.launchpad.fields import (
329@@ -90,7 +90,7 @@
330 class IDistributionPublic(
331 IBugTarget, ICanGetMilestonesDirectly, IHasAppointedDriver,
332 IHasBuildRecords, IHasDrivers, IHasMentoringOffers, IHasMilestones,
333- IHasOwner, IHasSecurityContact, IHasSprints, IHasTranslationGroup,
334+ IHasOwner, IHasSecurityContact, IHasSprints, ITranslationPolicy,
335 IKarmaContext, ILaunchpadUsage, IMakesAnnouncements,
336 IOfficialBugTagTargetPublic, IPillar, ISpecificationTarget):
337 """Public IDistribution properties."""
338
339=== modified file 'lib/lp/registry/interfaces/product.py'
340--- lib/lp/registry/interfaces/product.py 2010-01-20 13:58:45 +0000
341+++ lib/lp/registry/interfaces/product.py 2010-02-17 09:39:19 +0000
342@@ -49,7 +49,7 @@
343 from lp.registry.interfaces.karma import IKarmaContext
344 from canonical.launchpad.interfaces.launchpad import (
345 IHasAppointedDriver, IHasDrivers, IHasExternalBugTracker, IHasIcon,
346- IHasLogo, IHasMugshot,IHasSecurityContact, ILaunchpadUsage)
347+ IHasLogo, IHasMugshot, IHasSecurityContact, ILaunchpadUsage)
348 from lp.registry.interfaces.role import IHasOwner
349 from lp.registry.interfaces.milestone import (
350 ICanGetMilestonesDirectly, IHasMilestones)
351@@ -65,7 +65,7 @@
352 ISpecificationTarget)
353 from lp.blueprints.interfaces.sprint import IHasSprints
354 from lp.translations.interfaces.translationgroup import (
355- IHasTranslationGroup)
356+ ITranslationPolicy)
357 from canonical.launchpad.validators import LaunchpadValidationError
358 from canonical.launchpad.validators.name import name_validator
359 from canonical.launchpad.webapp.interfaces import NameLookupFailed
360@@ -179,8 +179,8 @@
361 'ACADEMIC', 'APACHE', 'ARTISTIC', 'ARTISTIC_2_0',
362 'BSD', 'COMMON_PUBLIC',
363 'CC_BY', 'CC_BY_SA', 'CC_0', 'ECLIPSE',
364- 'EDUCATIONAL_COMMUNITY', 'AFFERO', 'GNU_GPL_V2','GNU_GPL_V3',
365- 'GNU_LGPL_V2_1','GNU_LGPL_V3', 'MIT', 'MPL', 'OPEN_SOFTWARE', 'PERL',
366+ 'EDUCATIONAL_COMMUNITY', 'AFFERO', 'GNU_GPL_V2', 'GNU_GPL_V3',
367+ 'GNU_LGPL_V2_1', 'GNU_LGPL_V3', 'MIT', 'MPL', 'OPEN_SOFTWARE', 'PERL',
368 'PHP', 'PUBLIC_DOMAIN', 'PYTHON', 'ZPL',
369 'DONT_KNOW', 'OTHER_PROPRIETARY', 'OTHER_OPEN_SOURCE')
370
371@@ -338,7 +338,7 @@
372 IHasBranchVisibilityPolicy, IHasDrivers, IHasExternalBugTracker, IHasIcon,
373 IHasLogo, IHasMentoringOffers, IHasMergeProposals, IHasMilestones,
374 IHasMugshot, IHasOwner, IHasSecurityContact, IHasSprints,
375- IHasTranslationGroup, IKarmaContext, ILaunchpadUsage, IMakesAnnouncements,
376+ ITranslationPolicy, IKarmaContext, ILaunchpadUsage, IMakesAnnouncements,
377 IOfficialBugTagTargetPublic, IPillar, ISpecificationTarget):
378 """Public IProduct properties."""
379
380
381=== modified file 'lib/lp/registry/interfaces/project.py'
382--- lib/lp/registry/interfaces/project.py 2009-12-05 18:37:28 +0000
383+++ lib/lp/registry/interfaces/project.py 2010-02-17 09:39:19 +0000
384@@ -38,7 +38,7 @@
385 IHasSpecifications)
386 from lp.blueprints.interfaces.sprint import IHasSprints
387 from lp.translations.interfaces.translationgroup import (
388- IHasTranslationGroup)
389+ ITranslationPolicy)
390 from lp.registry.interfaces.structuralsubscription import (
391 IStructuralSubscriptionTarget)
392 from canonical.launchpad.validators.name import name_validator
393@@ -63,8 +63,8 @@
394 ICanGetMilestonesDirectly, IHasAppointedDriver, IHasBranches, IHasBugs,
395 IHasDrivers, IHasBranchVisibilityPolicy, IHasIcon, IHasLogo,
396 IHasMentoringOffers, IHasMergeProposals, IHasMilestones, IHasMugshot,
397- IHasOwner, IHasSpecifications, IHasSprints, IHasTranslationGroup,
398- IMakesAnnouncements, IKarmaContext, IPillar, IRootContext):
399+ IHasOwner, IHasSpecifications, IHasSprints, IMakesAnnouncements,
400+ IKarmaContext, IPillar, IRootContext):
401 """Public IProject properties."""
402
403 id = Int(title=_('ID'), readonly=True)
404@@ -279,7 +279,8 @@
405 """Return a ProjectSeries object with name `series_name`."""
406
407
408-class IProject(IProjectPublic, IStructuralSubscriptionTarget):
409+class IProject(IProjectPublic, IStructuralSubscriptionTarget,
410+ ITranslationPolicy):
411 """A Project."""
412
413 export_as_webservice_entry('project_group')
414
415=== modified file 'lib/lp/translations/browser/configure.zcml'
416--- lib/lp/translations/browser/configure.zcml 2010-02-10 13:04:10 +0000
417+++ lib/lp/translations/browser/configure.zcml 2010-02-17 09:39:19 +0000
418@@ -75,7 +75,7 @@
419 template="../templates/translations-macros.pt"
420 layer="canonical.launchpad.layers.TranslationsLayer"/>
421 <browser:page
422- for="lp.translations.interfaces.translationgroup.IHasTranslationGroup"
423+ for="lp.translations.interfaces.translationgroup.ITranslationPolicy"
424 facet="translations"
425 permission="zope.Public"
426 name="+portlet-translation-groups-and-permission"
427@@ -800,10 +800,10 @@
428 template="../templates/hastranslationimports-index.pt"
429 layer="canonical.launchpad.layers.TranslationsLayer"/>
430 <browser:page
431- name="+changetranslators"
432+ name="+settings"
433 for="lp.registry.interfaces.product.IProduct"
434- class="lp.translations.browser.product.ProductChangeTranslatorsView"
435- permission="launchpad.Edit"
436+ class="lp.translations.browser.product.ProductSettingsView"
437+ permission="launchpad.TranslationsAdmin"
438 template="../templates/set-translators.pt"
439 layer="canonical.launchpad.layers.TranslationsLayer"/>
440 <browser:pages
441@@ -853,10 +853,10 @@
442 class="lp.translations.browser.translations.HelpTranslateButtonView"
443 permission="zope.Public"/>
444 <browser:page
445- name="+changetranslators"
446+ name="+settings"
447 for="lp.registry.interfaces.project.IProject"
448- class="lp.translations.browser.project.ProjectChangeTranslatorsView"
449- permission="launchpad.Edit"
450+ class="lp.translations.browser.project.ProjectSettingsView"
451+ permission="launchpad.TranslationsAdmin"
452 template="../templates/set-translators.pt"
453 layer="canonical.launchpad.layers.TranslationsLayer"/>
454
455@@ -891,7 +891,7 @@
456 name="+settings"
457 for="lp.registry.interfaces.distribution.IDistribution"
458 class="lp.translations.browser.distribution.DistributionSettingsView"
459- permission="launchpad.Edit"
460+ permission="launchpad.TranslationsAdmin"
461 template="../templates/set-translators.pt"
462 layer="canonical.launchpad.layers.TranslationsLayer"/>
463 <browser:page
464
465=== modified file 'lib/lp/translations/browser/distribution.py'
466--- lib/lp/translations/browser/distribution.py 2009-12-13 11:55:40 +0000
467+++ lib/lp/translations/browser/distribution.py 2010-02-17 09:39:19 +0000
468@@ -35,10 +35,10 @@
469 link = canonical_url(self.context, rootsite='translations')
470 return Link(link, text)
471
472- @enabled_with_permission('launchpad.Edit')
473+ @enabled_with_permission('launchpad.TranslationsAdmin')
474 def settings(self):
475- text = 'Settings'
476- return Link('+settings', text)
477+ text = 'Change permissions'
478+ return Link('+settings', text, icon='edit')
479
480 @enabled_with_permission('launchpad.TranslationsAdmin')
481 def language_pack_admin(self):
482@@ -55,7 +55,6 @@
483
484 schema = IDistribution
485 label = "Select the language pack administrator"
486- page_title = "Set language pack administrator"
487 field_names = ['language_pack_admin']
488
489 @property
490@@ -111,7 +110,6 @@
491
492 class DistributionSettingsView(TranslationsMixin, DistributionEditView):
493 label = "Set permissions and policies"
494- page_title = "Permissions and policies"
495 field_names = ["translationgroup", "translationpermission"]
496
497 @property
498
499=== modified file 'lib/lp/translations/browser/potemplate.py'
500--- lib/lp/translations/browser/potemplate.py 2010-02-01 19:19:32 +0000
501+++ lib/lp/translations/browser/potemplate.py 2010-02-17 09:39:19 +0000
502@@ -257,7 +257,7 @@
503
504 @property
505 def group_parent(self):
506- """Return a parent object implementing `IHasTranslationGroups`."""
507+ """Return a parent object implementing `ITranslationPolicy`."""
508 if self.context.productseries is not None:
509 return self.context.productseries.product
510 else:
511
512=== modified file 'lib/lp/translations/browser/product.py'
513--- lib/lp/translations/browser/product.py 2010-02-03 10:37:32 +0000
514+++ lib/lp/translations/browser/product.py 2010-02-17 09:39:19 +0000
515@@ -6,7 +6,7 @@
516 __metaclass__ = type
517
518 __all__ = [
519- 'ProductChangeTranslatorsView',
520+ 'ProductSettingsView',
521 'ProductTranslationsMenu',
522 'ProductView',
523 ]
524@@ -38,10 +38,10 @@
525 text = 'Import queue'
526 return Link('+imports', text)
527
528- @enabled_with_permission('launchpad.Edit')
529+ @enabled_with_permission('launchpad.TranslationsAdmin')
530 def settings(self):
531- text = 'Settings'
532- return Link('+changetranslators', text, icon='edit')
533+ text = 'Change permissions'
534+ return Link('+settings', text, icon='edit')
535
536 @enabled_with_permission('launchpad.AnyPerson')
537 def translationdownload(self):
538@@ -62,13 +62,13 @@
539 return Link(link, text, icon='translation')
540
541
542-class ProductChangeTranslatorsView(TranslationsMixin, ProductEditView):
543+class ProductSettingsView(TranslationsMixin, ProductEditView):
544 label = "Set permissions and policies"
545 page_title = "Permissions and policies"
546 field_names = [
547 "translationgroup",
548 "translationpermission",
549- "translation_focus"
550+ "translation_focus",
551 ]
552
553 @property
554
555=== modified file 'lib/lp/translations/browser/project.py'
556--- lib/lp/translations/browser/project.py 2009-09-17 12:45:52 +0000
557+++ lib/lp/translations/browser/project.py 2010-02-17 09:39:19 +0000
558@@ -6,7 +6,7 @@
559 __metaclass__ = type
560
561 __all__ = [
562- 'ProjectChangeTranslatorsView',
563+ 'ProjectSettingsView',
564 'ProjectTranslationsMenu',
565 'ProjectView',
566 ]
567@@ -25,10 +25,10 @@
568 facet = 'translations'
569 links = ['products', 'settings', 'overview']
570
571- @enabled_with_permission('launchpad.Edit')
572+ @enabled_with_permission('launchpad.TranslationsAdmin')
573 def settings(self):
574- text = 'Settings'
575- return Link('+changetranslators', text, icon='edit')
576+ text = 'Change permissions'
577+ return Link('+settings', text, icon='edit')
578
579 def products(self):
580 text = 'Products'
581@@ -52,7 +52,7 @@
582 return list(all_products - translatables)
583
584
585-class ProjectChangeTranslatorsView(TranslationsMixin, ProjectEditView):
586+class ProjectSettingsView(TranslationsMixin, ProjectEditView):
587 label = "Set permissions and policies"
588 page_title = "Permissions and policies"
589 field_names = ["translationgroup", "translationpermission"]
590
591=== modified file 'lib/lp/translations/browser/translationgroup.py'
592--- lib/lp/translations/browser/translationgroup.py 2010-02-16 20:36:48 +0000
593+++ lib/lp/translations/browser/translationgroup.py 2010-02-17 09:39:19 +0000
594@@ -29,8 +29,7 @@
595 from canonical.launchpad.webapp.interfaces import NotFoundError
596 from canonical.launchpad.webapp import (
597 action, canonical_url, GetitemNavigation, LaunchpadEditFormView,
598- LaunchpadFormView
599- )
600+ LaunchpadFormView)
601 from canonical.launchpad.webapp.breadcrumb import Breadcrumb
602
603
604@@ -77,10 +76,10 @@
605 result.append({'lang': item.language.englishname,
606 'person': item.translator,
607 'code': item.language.code,
608- 'language' : item.language,
609+ 'language': item.language,
610 'datecreated': item.datecreated,
611 'style_guide_url': item.style_guide_url,
612- 'context' : item,
613+ 'context': item,
614 })
615 result.sort(key=operator.itemgetter('lang'))
616 return result
617
618=== modified file 'lib/lp/translations/interfaces/translationgroup.py'
619--- lib/lp/translations/interfaces/translationgroup.py 2009-08-24 15:35:23 +0000
620+++ lib/lp/translations/interfaces/translationgroup.py 2010-02-17 09:39:19 +0000
621@@ -8,7 +8,7 @@
622 __metaclass__ = type
623
624 __all__ = [
625- 'IHasTranslationGroup',
626+ 'ITranslationPolicy',
627 'ITranslationGroup',
628 'ITranslationGroupSet',
629 'TranslationPermission',
630@@ -74,7 +74,7 @@
631 to add suggestions.""")
632
633
634-class IHasTranslationGroup(Interface):
635+class ITranslationPolicy(Interface):
636 translationgroup = Choice(
637 title = _("Translation group"),
638 description = _("The translation group that helps review "
639
640=== modified file 'lib/lp/translations/stories/translationgroups/46-test-distro-structured-permissions.txt'
641--- lib/lp/translations/stories/translationgroups/46-test-distro-structured-permissions.txt 2009-09-12 07:25:21 +0000
642+++ lib/lp/translations/stories/translationgroups/46-test-distro-structured-permissions.txt 2010-02-17 09:39:19 +0000
643@@ -29,7 +29,7 @@
644 ... and its associated project, GNOME.
645
646 >>> admin_browser.open(
647- ... 'http://translations.launchpad.dev/gnome/+changetranslators')
648+ ... 'http://translations.launchpad.dev/gnome/+settings')
649 >>> admin_browser.getControl('Translation group').displayValue = [
650 ... 'Just a testing team']
651 >>> admin_browser.getControl(
652
653=== renamed file 'lib/lp/translations/stories/translationgroups/xx-product-translators.txt' => 'lib/lp/translations/stories/translationgroups/xx-change-translation-policy.txt'
654--- lib/lp/translations/stories/translationgroups/xx-product-translators.txt 2009-09-18 15:42:19 +0000
655+++ lib/lp/translations/stories/translationgroups/xx-change-translation-policy.txt 2010-02-17 09:39:19 +0000
656@@ -1,13 +1,93 @@
657-The translation work for a product can be assigned to a translation group.
658-
659- >>> admin_browser.open(
660- ... 'http://translations.launchpad.dev/evolution/+changetranslators')
661- >>> print admin_browser.title
662- Permissions and policies...
663-
664- >>> hint = find_tag_by_id(admin_browser.contents, 'form_extra_info')
665- >>> print hint
666- <p...
667+Translations policy settings
668+============================
669+
670+A product owner, Rosetta expert, and Ubuntu translations coordinator
671+browser is created.
672+
673+ >>> login('admin@canonical.com')
674+ >>> product_owner = factory.makePerson(
675+ ... email="po@ex.com", password="test")
676+ >>> chestii = factory.makeProduct(
677+ ... name='chestii', owner=product_owner, official_rosetta=True)
678+ >>> logout()
679+ >>> dtc_browser = setupDTCBrowser()
680+ >>> re_browser = setupRosettaExpertBrowser()
681+ >>> po_browser = setupBrowser("Basic po@ex.com:test")
682+
683+Visiting the main products translations page, product owners and Rosetta
684+administrators sees the "Change permissions" link, leading to the
685+translations settings page.
686+
687+ >>> re_browser.open(
688+ ... 'http://translations.launchpad.dev/chestii')
689+ >>> re_browser.getLink('Change permissions').click()
690+ >>> print re_browser.url
691+ http://translations.launchpad.dev/chestii/+settings
692+
693+ >>> po_browser.open(
694+ ... 'http://translations.launchpad.dev/chestii')
695+ >>> po_browser.getLink('Change permissions').click()
696+ >>> print po_browser.url
697+ http://translations.launchpad.dev/chestii/+settings
698+
699+From the settings page, translations group and translation permissions
700+can be changed.
701+
702+ >>> hint = find_tag_by_id(re_browser.contents, 'form_extra_info')
703+ >>> print extract_text(hint)
704 Select the translation group that will be managing...
705- ...The Evolution Groupware Application...
706- ...
707+
708+ >>> re_browser.getControl('Translation group').value = [
709+ ... 'ubuntu-translators']
710+ >>> re_browser.getControl('Translation permissions policy').value = [
711+ ... 'CLOSED']
712+ >>> re_browser.getControl('Change').click()
713+ >>> print re_browser.url
714+ http://translations.launchpad.dev/chestii
715+ >>> permissions = find_tag_by_id(
716+ ... re_browser.contents, 'translation-permissions')
717+ >>> print extract_text(permissions)
718+ Chestii is translated by Ubuntu Translators with Closed permissions.
719+
720+Other persons, including the translation group owners, will not see the link
721+to translations policy page.
722+
723+ >>> dtc_browser.open(
724+ ... 'http://translations.launchpad.dev/chestii')
725+ >>> dtc_browser.getLink('Change permissions')
726+ Traceback (most recent call last):
727+ ...
728+ LinkNotFoundError...
729+
730+An attempt to access the translations policy url will not be authorized.
731+
732+ >>> browser.open(
733+ ... 'http://translations.launchpad.dev/chestii/+settings')
734+ Traceback (most recent call last):
735+ ...
736+ Unauthorized...
737+
738+
739+Translations policy for distributions
740+-------------------------------------
741+
742+Ubuntu translation coordinators will have access to translations policy page
743+for Ubuntu and will be able to change it.
744+
745+ >>> dtc_browser.open(
746+ ... 'http://translations.launchpad.dev/ubuntu')
747+ >>> dtc_browser.getLink('Change permissions').click()
748+ >>> print dtc_browser.url
749+ http://translations.launchpad.dev/ubuntu/+settings
750+
751+ >>> dtc_browser.getControl('Translation group').value = [
752+ ... 'ubuntu-translators']
753+ >>> dtc_browser.getControl('Translation permissions policy').value = [
754+ ... 'CLOSED']
755+ >>> dtc_browser.getControl('Change').click()
756+ >>> print dtc_browser.url
757+ http://translations.launchpad.dev/ubuntu
758+ >>> permissions = find_tag_by_id(
759+ ... dtc_browser.contents, 'translation-permissions')
760+ >>> print extract_text(permissions)
761+ Ubuntu is translated by Ubuntu Translators with Closed permissions.
762
763=== modified file 'lib/lp/translations/templates/distribution-language-pack-admin-info.pt'
764--- lib/lp/translations/templates/distribution-language-pack-admin-info.pt 2009-09-13 20:24:35 +0000
765+++ lib/lp/translations/templates/distribution-language-pack-admin-info.pt 2010-02-17 09:39:19 +0000
766@@ -11,7 +11,7 @@
767 >Mr. No
768 </span>.
769 <tal:admin
770- condition="context/required:launchpad.Edit">
771+ condition="context/required:launchpad.TranslationsAdmin">
772 <a tal:attributes="
773 href
774 context/fmt:url/+select-language-pack-admin"
775@@ -19,7 +19,7 @@
776 </tal:admin>
777 </p>
778 <tal:admin
779- condition="context/required:launchpad.Edit">
780+ condition="context/required:launchpad.TranslationsAdmin">
781 <p tal:condition="not:context/language_pack_admin">
782 <a tal:attributes="
783 href
784
785=== modified file 'lib/lp/translations/templates/hastranslationgroup-portlet-translation-groups-and-permission.pt'
786--- lib/lp/translations/templates/hastranslationgroup-portlet-translation-groups-and-permission.pt 2009-09-10 18:47:57 +0000
787+++ lib/lp/translations/templates/hastranslationgroup-portlet-translation-groups-and-permission.pt 2010-02-17 09:39:19 +0000
788@@ -12,10 +12,9 @@
789 tal:content="context/translationpermission/title">certain</a>
790 permissions.
791 </p>
792- <div tal:condition="context/required:launchpad.Edit">
793- <a class="edit sprite"
794- tal:define="link context/menu:navigation/settings"
795- tal:attributes="href link/url"
796- >Change permissions</a>
797+ <div tal:condition="context/required:launchpad.TranslationsAdmin">
798+ <a tal:replace="structure context/menu:translations/settings/fmt:link">
799+ Change permissions
800+ </a>
801 </div>
802 </div>