Merge lp:~henninge/launchpad/devel-487137-custom-language-codes into lp:launchpad
- devel-487137-custom-language-codes
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Henning Eggers |
Approved revision: | no longer in the source branch. |
Merged at revision: | 12142 |
Proposed branch: | lp:~henninge/launchpad/devel-487137-custom-language-codes |
Merge into: | lp:launchpad |
Diff against target: |
561 lines (+175/-102) 9 files modified
lib/canonical/launchpad/security.py (+28/-0) lib/lp/translations/browser/configure.zcml (+4/-4) lib/lp/translations/browser/customlanguagecode.py (+10/-1) lib/lp/translations/stories/standalone/custom-language-codes.txt (+108/-91) lib/lp/translations/templates/customlanguagecode-index.pt (+1/-1) lib/lp/translations/templates/customlanguagecode-remove.pt (+18/-0) lib/lp/translations/templates/customlanguagecodes-index.pt (+4/-3) lib/lp/translations/templates/product-portlet-translatables.pt (+1/-1) lib/lp/translations/templates/sourcepackage-translations.pt (+1/-1) |
To merge this branch: | bzr merge lp:~henninge/launchpad/devel-487137-custom-language-codes |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Curtis Hovey (community) | ui | Approve | |
Jelmer Vernooij (community) | code | Approve | |
Review via email: mp+44446@code.launchpad.net |
Commit message
[r=jelmer]
Description of the change
= Summary =
This branch finishes off Adi's branch for the same bug which had
already been approved but was never landed. See the details here:
https:/
Also, the scope changed slightly because management of custom
language codes has now been extendended to project owners and
distribution translation teams respectively, so they can manage these
themselves. Great way to reduce the work load on admins and Launchapd
devs. ;-)
== Proposed fix ==
Remove AdminCustomLang
bug (and a good reason for not landing Adi's branch) as it shadowed
AdminProductTra
product owners. AdminProductTra
to allow access to owners & Rosetta admins.
Add AdminDistributi
launchpad.
to the same persons that have it on the distribution.
Fix AdminCustomLang
distribution translation teams.
Update the page test to show the new behavior.
== Pre-implementation notes ==
I read through the previous mp and saw that it was well done. I did
some experiementing to figure out what was wrong with the branch and
found out about the shadowing issue. I talked to Danilo and we
decided to extendend privileges to project owners.
== Implementation details ==
AdminProductTra
the right thing and are used by the new adapters.
A custom language code can either be linked to a Product or a
DistributionSou
sourcepacakgename), so that the security adapter has to select the
right adapter to forward to.
AdminCustomLang
"distribution" attribute, so it could be used, too.
The test was re-written to use the owner_browser for all actions on a
Product. For the DistributionSou
to be added because its owner will have translation admin privileges
within the distribution. A new translations_
this person.
== Tests ==
bin/test -vvcm lp.translations -t custom-
== Demo and Q/A ==
On launchpad.dev login as <email address hidden> (name12) and go to this
page: https:/
You should see the "define custom language codes" link.
Click on it and play around with the admin interface.
= Launchpad lint =
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/canonical
lib/lp/
lib/lp/
lib/lp/
lib/lp/
Henning Eggers (henninge) wrote : | # |
1 | === modified file 'lib/canonical/launchpad/security.py' |
2 | --- lib/canonical/launchpad/security.py 2010-12-21 15:13:17 +0000 |
3 | +++ lib/canonical/launchpad/security.py 2010-12-22 12:03:59 +0000 |
4 | @@ -185,7 +185,6 @@ |
5 | from lp.soyuz.interfaces.sourcepackagerelease import ISourcePackageRelease |
6 | from lp.translations.interfaces.customlanguagecode import ( |
7 | ICustomLanguageCode, |
8 | - IHasCustomLanguageCodes, |
9 | ) |
10 | from lp.translations.interfaces.languagepack import ILanguagePack |
11 | from lp.translations.interfaces.pofile import IPOFile |
12 | @@ -744,6 +743,7 @@ |
13 | # with launchpad.View so that this adapter is used. For now, though, it's |
14 | # going to be used only on the webservice (which explicitly checks for |
15 | # launchpad.View) so that we don't leak memberships of private teams. |
16 | + |
17 | class ViewTeamMembership(AuthorizationBase): |
18 | permission = 'launchpad.View' |
19 | usedfor = ITeamMembership |
20 | @@ -1728,27 +1728,23 @@ |
21 | usedfor = ILanguage |
22 | |
23 | |
24 | -class AdminCustomLanguageCodes(OnlyRosettaExpertsAndAdmins): |
25 | - """Controls administration of custom language codes. |
26 | - |
27 | - Rosetta experts and Launchpad administrators can administer custom |
28 | - language codes. |
29 | - """ |
30 | - |
31 | - permission = 'launchpad.TranslationsAdmin' |
32 | - usedfor = IHasCustomLanguageCodes |
33 | - |
34 | - |
35 | -class AdminCustomLanguageCode(OnlyRosettaExpertsAndAdmins): |
36 | +class AdminCustomLanguageCode(AuthorizationBase): |
37 | """Controls administration for a custom language code. |
38 | |
39 | - Rosetta experts and Launchpad administrators can administer a custom |
40 | - language code. |
41 | + Whoever can admin a product's or distribution's translations can also |
42 | + admin the custom language codes for it. |
43 | """ |
44 | - |
45 | permission = 'launchpad.TranslationsAdmin' |
46 | usedfor = ICustomLanguageCode |
47 | |
48 | + def checkAuthenticated(self, user): |
49 | + if self.obj.product is not None: |
50 | + return AdminProductTranslations( |
51 | + self.obj.product).checkAuthenticated(user) |
52 | + else: |
53 | + return AdminDistributionTranslations( |
54 | + self.obj.distribution).checkAuthenticated(user) |
55 | + |
56 | |
57 | class AccessBranch(AuthorizationBase): |
58 | """Controls visibility of branches. |
59 | @@ -1839,6 +1835,12 @@ |
60 | self.obj.distribution).checkAuthenticated(user)) |
61 | |
62 | |
63 | +class AdminDistributionSourcePackageTranslations( |
64 | + AdminDistroSeriesTranslations): |
65 | + """DistributionSourcePackage objects link to a distribution, too.""" |
66 | + usedfor = IDistributionSourcePackage |
67 | + |
68 | + |
69 | class AdminProductSeriesTranslations(AuthorizationBase): |
70 | permission = 'launchpad.TranslationsAdmin' |
71 | usedfor = IProductSeries |
72 | |
73 | === modified file 'lib/lp/translations/browser/configure.zcml' |
74 | --- lib/lp/translations/browser/configure.zcml 2010-12-21 15:13:17 +0000 |
75 | +++ lib/lp/translations/browser/configure.zcml 2010-12-22 12:01:59 +0000 |
76 | @@ -941,7 +941,7 @@ |
77 | for="lp.registry.interfaces.distribution.IDistribution" |
78 | permission="zope.Public" |
79 | template="../templates/translations-portlet-not-using-launchpad-extra.pt" |
80 | - layer="lp.translations.publisher.TranslationsLayer"/> |
81 | + layer="lp.translations.publisher.TranslationsLayer"/> |
82 | <browser:page |
83 | name="+portlet-configuration" |
84 | for="lp.registry.interfaces.distribution.IDistribution" |
85 | |
86 | === modified file 'lib/lp/translations/browser/customlanguagecode.py' |
87 | --- lib/lp/translations/browser/customlanguagecode.py 2010-12-21 15:13:17 +0000 |
88 | +++ lib/lp/translations/browser/customlanguagecode.py 2010-12-22 12:03:05 +0000 |
89 | @@ -46,6 +46,7 @@ |
90 | |
91 | class CustomLanguageCodeBreadcrumb(Breadcrumb): |
92 | """Breadcrumb for a `CustomLanguageCode`.""" |
93 | + |
94 | @property |
95 | def text(self): |
96 | return smartquote( |
97 | @@ -164,6 +165,7 @@ |
98 | class HasCustomLanguageCodesTraversalMixin: |
99 | """Navigate from an `IHasCustomLanguageCodes` to a `CustomLanguageCode`. |
100 | """ |
101 | + |
102 | @stepthrough('+customcode') |
103 | def traverseCustomCode(self, name): |
104 | """Traverse +customcode URLs.""" |
105 | |
106 | === modified file 'lib/lp/translations/stories/standalone/custom-language-codes.txt' |
107 | --- lib/lp/translations/stories/standalone/custom-language-codes.txt 2010-12-21 15:13:17 +0000 |
108 | +++ lib/lp/translations/stories/standalone/custom-language-codes.txt 2010-12-22 12:04:22 +0000 |
109 | @@ -38,16 +38,18 @@ |
110 | >>> owner_browser = setupBrowser("Basic o@example.com:test") |
111 | >>> rosetta_admin_browser = setupRosettaExpertBrowser() |
112 | |
113 | -A Launchpad administrator or Rosetta expert sees the link to the custom |
114 | -language codes on a project's main translations page. |
115 | +The project's owner sees the link to the custom language codes on a project's |
116 | +main translations page. |
117 | |
118 | - >>> admin_browser.open(product_page) |
119 | - >>> tag = find_custom_language_codes_link(admin_browser) |
120 | + >>> owner_browser.open(product_page) |
121 | + >>> tag = find_custom_language_codes_link(owner_browser) |
122 | >>> print extract_text(tag.renderContents()) |
123 | If necessary, you may |
124 | define custom language codes |
125 | for this project. |
126 | |
127 | +Translation admins also have access to this link. |
128 | + |
129 | >>> rosetta_admin_browser.open(product_page) |
130 | >>> tag = find_custom_language_codes_link(rosetta_admin_browser) |
131 | >>> print extract_text(tag.renderContents()) |
132 | @@ -57,39 +59,37 @@ |
133 | |
134 | The link goes to the custom language codes management page. |
135 | |
136 | - >>> rosetta_admin_browser.getLink("define custom language codes").click() |
137 | - >>> custom_language_codes_page = rosetta_admin_browser.url |
138 | - |
139 | -Non-admins, even the project's owner, don't see this link. We do not |
140 | -advertise this feature, since the proper solution is generally to use |
141 | -the right language codes. |
142 | - |
143 | - >>> owner_browser.open(product_page) |
144 | - >>> print find_custom_language_codes_link(owner_browser) |
145 | + >>> owner_browser.getLink("define custom language codes").click() |
146 | + >>> custom_language_codes_page = owner_browser.url |
147 | + |
148 | +Other users don't see this link. |
149 | + |
150 | + >>> user_browser.open(product_page) |
151 | + >>> print find_custom_language_codes_link(user_browser) |
152 | None |
153 | |
154 | Initially the page shows no custom language codes for the project. |
155 | |
156 | - >>> tag = find_tag_by_id(rosetta_admin_browser.contents, 'empty') |
157 | + >>> tag = find_tag_by_id(owner_browser.contents, 'empty') |
158 | >>> print extract_text(tag.renderContents()) |
159 | No custom language codes have been defined. |
160 | |
161 | -The admin can add a custom language code. |
162 | - |
163 | - >>> rosetta_admin_browser.getLink("Add a custom language code").click() |
164 | - >>> add_page = rosetta_admin_browser.url |
165 | - |
166 | - >>> rosetta_admin_browser.getControl("Language code:").value = 'no' |
167 | - >>> rosetta_admin_browser.getControl("Language:").value = ['nn'] |
168 | - >>> rosetta_admin_browser.getControl("Add").click() |
169 | +There is a link to add a custom language code. |
170 | + |
171 | + >>> owner_browser.getLink("Add a custom language code").click() |
172 | + >>> add_page = owner_browser.url |
173 | + |
174 | + >>> owner_browser.getControl("Language code:").value = 'no' |
175 | + >>> owner_browser.getControl("Language:").value = ['nn'] |
176 | + >>> owner_browser.getControl("Add").click() |
177 | |
178 | This leads back to the custom language codes overview, where the new |
179 | code is now shown. |
180 | |
181 | - >>> rosetta_admin_browser.url == custom_language_codes_page |
182 | + >>> owner_browser.url == custom_language_codes_page |
183 | True |
184 | |
185 | - >>> tag = find_tag_by_id(rosetta_admin_browser.contents, 'nonempty') |
186 | + >>> tag = find_tag_by_id(owner_browser.contents, 'nonempty') |
187 | >>> print extract_text(tag.renderContents()) |
188 | Foo uses the following custom language codes: |
189 | Code... ...maps to language |
190 | @@ -98,8 +98,8 @@ |
191 | There is an overview page for the custom code, though there's not much |
192 | to see there. |
193 | |
194 | - >>> rosetta_admin_browser.getLink("no").click() |
195 | - >>> main = find_main_content(rosetta_admin_browser.contents) |
196 | + >>> owner_browser.getLink("no").click() |
197 | + >>> main = find_main_content(owner_browser.contents) |
198 | >>> print extract_text(main.renderContents()) |
199 | Foo Translations Custom language code ...no... |
200 | For Foo, uploads with the language code |
201 | @@ -111,79 +111,79 @@ |
202 | |
203 | The overview page leads back to the custom language codes overview. |
204 | |
205 | - >>> code_page = rosetta_admin_browser.url |
206 | - >>> rosetta_admin_browser.getLink( |
207 | + >>> code_page = owner_browser.url |
208 | + >>> owner_browser.getLink( |
209 | ... "custom language codes overview").click() |
210 | - >>> rosetta_admin_browser.url == custom_language_codes_page |
211 | + >>> owner_browser.url == custom_language_codes_page |
212 | True |
213 | |
214 | - >>> rosetta_admin_browser.open(code_page) |
215 | + >>> owner_browser.open(code_page) |
216 | |
217 | -There is also a link for removing codes. The admin follows the link and |
218 | +There is also a link for removing codes. The owner follows the link and |
219 | removes the "no" custom language code. |
220 | |
221 | - >>> rosetta_admin_browser.getLink("remove custom language code").click() |
222 | - >>> remove_page = rosetta_admin_browser.url |
223 | - >>> rosetta_admin_browser.getControl("Remove").click() |
224 | + >>> owner_browser.getLink("remove custom language code").click() |
225 | + >>> remove_page = owner_browser.url |
226 | + >>> owner_browser.getControl("Remove").click() |
227 | |
228 | This leads back to the overview page. |
229 | |
230 | - >>> rosetta_admin_browser.url == custom_language_codes_page |
231 | + >>> owner_browser.url == custom_language_codes_page |
232 | True |
233 | |
234 | - >>> tag = find_tag_by_id(rosetta_admin_browser.contents, 'empty') |
235 | - >>> print extract_text(tag.renderContents()) |
236 | - No custom language codes have been defined. |
237 | - |
238 | - |
239 | -Non-admin access |
240 | -================ |
241 | - |
242 | -A non-admin can see the page, actually, if they know the URL. This can |
243 | -be convenient for debugging. |
244 | - |
245 | - >>> owner_browser.open(custom_language_codes_page) |
246 | - |
247 | >>> tag = find_tag_by_id(owner_browser.contents, 'empty') |
248 | >>> print extract_text(tag.renderContents()) |
249 | No custom language codes have been defined. |
250 | |
251 | + |
252 | +Unprivileged access |
253 | +=================== |
254 | + |
255 | +A unprivileged user can see the page, actually, if they know the URL. |
256 | +This can be convenient for debugging. |
257 | + |
258 | + >>> user_browser.open(custom_language_codes_page) |
259 | + |
260 | + >>> tag = find_tag_by_id(user_browser.contents, 'empty') |
261 | + >>> print extract_text(tag.renderContents()) |
262 | + No custom language codes have been defined. |
263 | + |
264 | However all they get is a read-only version of the page. |
265 | |
266 | - >>> owner_browser.getLink("Add a custom language code").click() |
267 | + >>> user_browser.getLink("Add a custom language code").click() |
268 | Traceback (most recent call last): |
269 | ... |
270 | LinkNotFoundError |
271 | |
272 | The page for adding custom language codes is not accessible to them. |
273 | |
274 | + >>> user_browser.open(add_page) |
275 | + Traceback (most recent call last): |
276 | + ... |
277 | + Unauthorized: ... |
278 | + |
279 | +And naturally, if the owner creates a custom language code again, an |
280 | +unprivileged user can't remove it. |
281 | + |
282 | >>> owner_browser.open(add_page) |
283 | - Traceback (most recent call last): |
284 | - ... |
285 | - Unauthorized: ... |
286 | - |
287 | -And naturally, if an admin creates a custom language code again, a |
288 | -non-admin can't remove it. |
289 | - |
290 | - >>> rosetta_admin_browser.open(add_page) |
291 | - >>> rosetta_admin_browser.getControl("Language code:").value = 'no' |
292 | - >>> rosetta_admin_browser.getControl("Language:").value = ['nn'] |
293 | - >>> rosetta_admin_browser.getControl("Add").click() |
294 | - |
295 | - >>> owner_browser.open(custom_language_codes_page) |
296 | - >>> tag = find_tag_by_id(owner_browser.contents, 'nonempty') |
297 | + >>> owner_browser.getControl("Language code:").value = 'no' |
298 | + >>> owner_browser.getControl("Language:").value = ['nn'] |
299 | + >>> owner_browser.getControl("Add").click() |
300 | + |
301 | + >>> user_browser.open(custom_language_codes_page) |
302 | + >>> tag = find_tag_by_id(user_browser.contents, 'nonempty') |
303 | >>> print extract_text(tag.renderContents()) |
304 | Foo uses the following custom language codes: |
305 | Code... ...maps to language |
306 | no Norwegian Nynorsk |
307 | |
308 | - >>> owner_browser.getLink("no").click() |
309 | - >>> owner_browser.getLink("remove custom language code") |
310 | + >>> user_browser.getLink("no").click() |
311 | + >>> user_browser.getLink("remove custom language code") |
312 | Traceback (most recent call last): |
313 | ... |
314 | LinkNotFoundError |
315 | |
316 | - >>> owner_browser.open(remove_page) |
317 | + >>> user_browser.open(remove_page) |
318 | Traceback (most recent call last): |
319 | ... |
320 | Unauthorized: ... |
321 | @@ -197,7 +197,8 @@ |
322 | package--i.e. the combination of a distribution and a source package |
323 | name. However, since there is no Translations page for that type of |
324 | object (and we'd probably never go there if there were), the link is |
325 | -shown on the source package page. |
326 | +shown on the source package page. For distributions, the owner of the |
327 | +distribution's translation group is a translations administrator. |
328 | |
329 | >>> login(ANONYMOUS) |
330 | >>> from lp.registry.model.sourcepackage import SourcePackage |
331 | @@ -219,38 +220,44 @@ |
332 | ... distroseries=other_series, |
333 | ... sourcepackagename=package.sourcepackagename), |
334 | ... rootsite="translations") |
335 | + >>> translations_admin = factory.makePerson( |
336 | + ... email='ta@example.com', password='test') |
337 | + >>> translationgroup = factory.makeTranslationGroup( |
338 | + ... owner=translations_admin) |
339 | + >>> removeSecurityProxy(distro).translationgroup = translationgroup |
340 | >>> logout() |
341 | |
342 | - >>> rosetta_admin_browser.open(package_page) |
343 | + >>> translations_browser = setupBrowser("Basic ta@example.com:test") |
344 | + >>> translations_browser.open(package_page) |
345 | |
346 | Of course in this case, the notice about there being no custom language |
347 | codes talks about a package, not a project. |
348 | |
349 | - >>> tag = find_custom_language_codes_link(rosetta_admin_browser) |
350 | + >>> tag = find_custom_language_codes_link(translations_browser) |
351 | >>> print extract_text(tag.renderContents()) |
352 | If necessary, you may |
353 | define custom language codes |
354 | for this package. |
355 | |
356 | - >>> rosetta_admin_browser.getLink("define custom language codes").click() |
357 | - >>> custom_language_codes_page = rosetta_admin_browser.url |
358 | + >>> translations_browser.getLink("define custom language codes").click() |
359 | + >>> custom_language_codes_page = translations_browser.url |
360 | |
361 | - >>> tag = find_tag_by_id(rosetta_admin_browser.contents, 'empty') |
362 | + >>> tag = find_tag_by_id(translations_browser.contents, 'empty') |
363 | >>> print extract_text(tag.renderContents()) |
364 | No custom language codes have been defined. |
365 | |
366 | -Again, an admin can add a language code. |
367 | - |
368 | - >>> rosetta_admin_browser.getLink("Add a custom language code").click() |
369 | - >>> add_page = rosetta_admin_browser.url |
370 | - |
371 | - >>> rosetta_admin_browser.getControl("Language code:").value = 'pt-br' |
372 | - >>> rosetta_admin_browser.getControl("Language:").value = ['pt_BR'] |
373 | - >>> rosetta_admin_browser.getControl("Add").click() |
374 | +A translations admin can add a language code. |
375 | + |
376 | + >>> translations_browser.getLink("Add a custom language code").click() |
377 | + >>> add_page = translations_browser.url |
378 | + |
379 | + >>> translations_browser.getControl("Language code:").value = 'pt-br' |
380 | + >>> translations_browser.getControl("Language:").value = ['pt_BR'] |
381 | + >>> translations_browser.getControl("Add").click() |
382 | |
383 | The language code is displayed. |
384 | |
385 | - >>> tag = find_tag_by_id(rosetta_admin_browser.contents, 'nonempty') |
386 | + >>> tag = find_tag_by_id(translations_browser.contents, 'nonempty') |
387 | >>> print extract_text(tag.renderContents()) |
388 | bar in distro uses the following custom language codes: |
389 | Code... ...maps to language |
390 | @@ -259,15 +266,15 @@ |
391 | It's also displayed identically on the same package but in another |
392 | release series of the same distribution. |
393 | |
394 | - >>> rosetta_admin_browser.open(page_in_other_series) |
395 | - >>> tag = find_custom_language_codes_link(rosetta_admin_browser) |
396 | + >>> translations_browser.open(page_in_other_series) |
397 | + >>> tag = find_custom_language_codes_link(translations_browser) |
398 | >>> print extract_text(tag.renderContents()) |
399 | If necessary, you may |
400 | define custom language codes |
401 | for this package. |
402 | |
403 | - >>> rosetta_admin_browser.getLink("define custom language codes").click() |
404 | - >>> tag = find_tag_by_id(rosetta_admin_browser.contents, 'nonempty') |
405 | + >>> translations_browser.getLink("define custom language codes").click() |
406 | + >>> tag = find_tag_by_id(translations_browser.contents, 'nonempty') |
407 | >>> print extract_text(tag.renderContents()) |
408 | bar in distro uses the following custom language codes: |
409 | Code... ...maps to language |
410 | @@ -276,13 +283,13 @@ |
411 | |
412 | The new code has a link there... |
413 | |
414 | - >>> rosetta_admin_browser.getLink("pt-br").click() |
415 | + >>> translations_browser.getLink("pt-br").click() |
416 | |
417 | ...and can be deleted. |
418 | |
419 | - >>> rosetta_admin_browser.getLink("remove custom language code").click() |
420 | - >>> rosetta_admin_browser.getControl("Remove").click() |
421 | + >>> translations_browser.getLink("remove custom language code").click() |
422 | + >>> translations_browser.getControl("Remove").click() |
423 | |
424 | - >>> tag = find_tag_by_id(rosetta_admin_browser.contents, 'empty') |
425 | + >>> tag = find_tag_by_id(translations_browser.contents, 'empty') |
426 | >>> print extract_text(tag.renderContents()) |
427 | No custom language codes have been defined. |
Jelmer Vernooij (jelmer) : | # |
Henning Eggers (henninge) wrote : | # |
I just realized that this branch needs a UI review, too. Here is a walk-through with some screen shots.
Starting on a product's translation page as the products owner. You see the link to "define custom languge codes".
http://
Clicking that link will bring you to the listing of custom language codes for this product which is still empty.
http://
Clicking "Add a custom language code" will bring you to the add form.
http://
Now the listing has one entry.
http://
Clicking on the custom code brings up the details page for this code.
http://
And trying to remove it will bring up the removal page.
http://
Almost could have done a screencast ... ;)
Curtis Hovey (sinzui) wrote : | # |
Hi Henning.
This looks good. I have a question. Why is custom_
Henning Eggers (henninge) wrote : | # |
Good catch, thank you! Here is a screenshot of the fixed view.
http://
Curtis Hovey (sinzui) wrote : | # |
I think this is good to land.
Preview Diff
1 | === modified file 'lib/canonical/launchpad/security.py' |
2 | --- lib/canonical/launchpad/security.py 2010-12-17 20:28:40 +0000 |
3 | +++ lib/canonical/launchpad/security.py 2010-12-23 09:44:44 +0000 |
4 | @@ -183,6 +183,9 @@ |
5 | IPackageUploadQueue, |
6 | ) |
7 | from lp.soyuz.interfaces.sourcepackagerelease import ISourcePackageRelease |
8 | +from lp.translations.interfaces.customlanguagecode import ( |
9 | + ICustomLanguageCode, |
10 | + ) |
11 | from lp.translations.interfaces.languagepack import ILanguagePack |
12 | from lp.translations.interfaces.pofile import IPOFile |
13 | from lp.translations.interfaces.potemplate import IPOTemplate |
14 | @@ -740,6 +743,7 @@ |
15 | # with launchpad.View so that this adapter is used. For now, though, it's |
16 | # going to be used only on the webservice (which explicitly checks for |
17 | # launchpad.View) so that we don't leak memberships of private teams. |
18 | + |
19 | class ViewTeamMembership(AuthorizationBase): |
20 | permission = 'launchpad.View' |
21 | usedfor = ITeamMembership |
22 | @@ -1724,6 +1728,24 @@ |
23 | usedfor = ILanguage |
24 | |
25 | |
26 | +class AdminCustomLanguageCode(AuthorizationBase): |
27 | + """Controls administration for a custom language code. |
28 | + |
29 | + Whoever can admin a product's or distribution's translations can also |
30 | + admin the custom language codes for it. |
31 | + """ |
32 | + permission = 'launchpad.TranslationsAdmin' |
33 | + usedfor = ICustomLanguageCode |
34 | + |
35 | + def checkAuthenticated(self, user): |
36 | + if self.obj.product is not None: |
37 | + return AdminProductTranslations( |
38 | + self.obj.product).checkAuthenticated(user) |
39 | + else: |
40 | + return AdminDistributionTranslations( |
41 | + self.obj.distribution).checkAuthenticated(user) |
42 | + |
43 | + |
44 | class AccessBranch(AuthorizationBase): |
45 | """Controls visibility of branches. |
46 | |
47 | @@ -1813,6 +1835,12 @@ |
48 | self.obj.distribution).checkAuthenticated(user)) |
49 | |
50 | |
51 | +class AdminDistributionSourcePackageTranslations( |
52 | + AdminDistroSeriesTranslations): |
53 | + """DistributionSourcePackage objects link to a distribution, too.""" |
54 | + usedfor = IDistributionSourcePackage |
55 | + |
56 | + |
57 | class AdminProductSeriesTranslations(AuthorizationBase): |
58 | permission = 'launchpad.TranslationsAdmin' |
59 | usedfor = IProductSeries |
60 | |
61 | === modified file 'lib/lp/translations/browser/configure.zcml' |
62 | --- lib/lp/translations/browser/configure.zcml 2010-12-01 11:26:57 +0000 |
63 | +++ lib/lp/translations/browser/configure.zcml 2010-12-23 09:44:44 +0000 |
64 | @@ -941,7 +941,7 @@ |
65 | for="lp.registry.interfaces.distribution.IDistribution" |
66 | permission="zope.Public" |
67 | template="../templates/translations-portlet-not-using-launchpad-extra.pt" |
68 | - layer="lp.translations.publisher.TranslationsLayer"/> |
69 | + layer="lp.translations.publisher.TranslationsLayer"/> |
70 | <browser:page |
71 | name="+portlet-configuration" |
72 | for="lp.registry.interfaces.distribution.IDistribution" |
73 | @@ -1042,9 +1042,9 @@ |
74 | <browser:page |
75 | name="+remove" |
76 | for="lp.translations.interfaces.customlanguagecode.ICustomLanguageCode" |
77 | - permission="launchpad.Admin" |
78 | + permission="launchpad.TranslationsAdmin" |
79 | class="lp.translations.browser.customlanguagecode.CustomLanguageCodeRemoveView" |
80 | - template="../../app/templates/generic-edit.pt" |
81 | + template="../templates/customlanguagecode-remove.pt" |
82 | layer="lp.translations.publisher.TranslationsLayer"/> |
83 | |
84 | <!-- IHasCustomLanguageCodes --> |
85 | @@ -1062,7 +1062,7 @@ |
86 | layer="lp.translations.publisher.TranslationsLayer" |
87 | class="lp.translations.browser.customlanguagecode.CustomLanguageCodeAddView" |
88 | template="../templates/customlanguagecode-add.pt" |
89 | - permission="launchpad.Admin"/> |
90 | + permission="launchpad.TranslationsAdmin"/> |
91 | |
92 | </facet> |
93 | |
94 | |
95 | === modified file 'lib/lp/translations/browser/customlanguagecode.py' |
96 | --- lib/lp/translations/browser/customlanguagecode.py 2010-12-01 11:26:57 +0000 |
97 | +++ lib/lp/translations/browser/customlanguagecode.py 2010-12-23 09:44:44 +0000 |
98 | @@ -11,7 +11,7 @@ |
99 | 'CustomLanguageCodeView', |
100 | 'HasCustomLanguageCodesNavigation', |
101 | 'HasCustomLanguageCodesTraversalMixin', |
102 | - ] |
103 | + ] |
104 | |
105 | |
106 | import re |
107 | @@ -46,6 +46,7 @@ |
108 | |
109 | class CustomLanguageCodeBreadcrumb(Breadcrumb): |
110 | """Breadcrumb for a `CustomLanguageCode`.""" |
111 | + |
112 | @property |
113 | def text(self): |
114 | return smartquote( |
115 | @@ -126,6 +127,13 @@ |
116 | class CustomLanguageCodeView(LaunchpadView): |
117 | schema = ICustomLanguageCode |
118 | |
119 | + @property |
120 | + def label(self): |
121 | + target_displayname = self.context.translation_target.displayname |
122 | + return smartquote( |
123 | + 'Custom language code "%s" for %s' % ( |
124 | + self.context.language_code, target_displayname)) |
125 | + |
126 | |
127 | class CustomLanguageCodeRemoveView(LaunchpadFormView): |
128 | """View for removing a `CustomLanguageCode`.""" |
129 | @@ -164,6 +172,7 @@ |
130 | class HasCustomLanguageCodesTraversalMixin: |
131 | """Navigate from an `IHasCustomLanguageCodes` to a `CustomLanguageCode`. |
132 | """ |
133 | + |
134 | @stepthrough('+customcode') |
135 | def traverseCustomCode(self, name): |
136 | """Traverse +customcode URLs.""" |
137 | |
138 | === modified file 'lib/lp/translations/stories/standalone/custom-language-codes.txt' |
139 | --- lib/lp/translations/stories/standalone/custom-language-codes.txt 2010-08-31 23:03:45 +0000 |
140 | +++ lib/lp/translations/stories/standalone/custom-language-codes.txt 2010-12-23 09:44:44 +0000 |
141 | @@ -36,12 +36,22 @@ |
142 | >>> logout() |
143 | |
144 | >>> owner_browser = setupBrowser("Basic o@example.com:test") |
145 | - |
146 | -An administrator sees the link to the custom language codes on a |
147 | -project's main translations page. |
148 | - |
149 | - >>> admin_browser.open(product_page) |
150 | - >>> tag = find_custom_language_codes_link(admin_browser) |
151 | + >>> rosetta_admin_browser = setupRosettaExpertBrowser() |
152 | + |
153 | +The project's owner sees the link to the custom language codes on a project's |
154 | +main translations page. |
155 | + |
156 | + >>> owner_browser.open(product_page) |
157 | + >>> tag = find_custom_language_codes_link(owner_browser) |
158 | + >>> print extract_text(tag.renderContents()) |
159 | + If necessary, you may |
160 | + define custom language codes |
161 | + for this project. |
162 | + |
163 | +Translation admins also have access to this link. |
164 | + |
165 | + >>> rosetta_admin_browser.open(product_page) |
166 | + >>> tag = find_custom_language_codes_link(rosetta_admin_browser) |
167 | >>> print extract_text(tag.renderContents()) |
168 | If necessary, you may |
169 | define custom language codes |
170 | @@ -49,39 +59,37 @@ |
171 | |
172 | The link goes to the custom language codes management page. |
173 | |
174 | - >>> admin_browser.getLink("define custom language codes").click() |
175 | - >>> custom_language_codes_page = admin_browser.url |
176 | - |
177 | -Non-admins, even the project's owner, don't see this link. We do not |
178 | -advertise this feature, since the proper solution is generally to use |
179 | -the right language codes. |
180 | - |
181 | - >>> owner_browser.open(product_page) |
182 | - >>> print find_custom_language_codes_link(owner_browser) |
183 | + >>> owner_browser.getLink("define custom language codes").click() |
184 | + >>> custom_language_codes_page = owner_browser.url |
185 | + |
186 | +Other users don't see this link. |
187 | + |
188 | + >>> user_browser.open(product_page) |
189 | + >>> print find_custom_language_codes_link(user_browser) |
190 | None |
191 | |
192 | Initially the page shows no custom language codes for the project. |
193 | |
194 | - >>> tag = find_tag_by_id(admin_browser.contents, 'empty') |
195 | + >>> tag = find_tag_by_id(owner_browser.contents, 'empty') |
196 | >>> print extract_text(tag.renderContents()) |
197 | No custom language codes have been defined. |
198 | |
199 | -The admin can add a custom language code. |
200 | - |
201 | - >>> admin_browser.getLink("Add a custom language code").click() |
202 | - >>> add_page = admin_browser.url |
203 | - |
204 | - >>> admin_browser.getControl("Language code:").value = 'no' |
205 | - >>> admin_browser.getControl("Language:").value = ['nn'] |
206 | - >>> admin_browser.getControl("Add").click() |
207 | +There is a link to add a custom language code. |
208 | + |
209 | + >>> owner_browser.getLink("Add a custom language code").click() |
210 | + >>> add_page = owner_browser.url |
211 | + |
212 | + >>> owner_browser.getControl("Language code:").value = 'no' |
213 | + >>> owner_browser.getControl("Language:").value = ['nn'] |
214 | + >>> owner_browser.getControl("Add").click() |
215 | |
216 | This leads back to the custom language codes overview, where the new |
217 | code is now shown. |
218 | |
219 | - >>> admin_browser.url == custom_language_codes_page |
220 | + >>> owner_browser.url == custom_language_codes_page |
221 | True |
222 | |
223 | - >>> tag = find_tag_by_id(admin_browser.contents, 'nonempty') |
224 | + >>> tag = find_tag_by_id(owner_browser.contents, 'nonempty') |
225 | >>> print extract_text(tag.renderContents()) |
226 | Foo uses the following custom language codes: |
227 | Code... ...maps to language |
228 | @@ -90,9 +98,10 @@ |
229 | There is an overview page for the custom code, though there's not much |
230 | to see there. |
231 | |
232 | - >>> admin_browser.getLink("no").click() |
233 | - >>> main = find_main_content(admin_browser.contents) |
234 | + >>> owner_browser.getLink("no").click() |
235 | + >>> main = find_main_content(owner_browser.contents) |
236 | >>> print extract_text(main.renderContents()) |
237 | + Custom language code ...no... for Foo |
238 | Foo Translations Custom language code ...no... |
239 | For Foo, uploads with the language code |
240 | “no” |
241 | @@ -103,78 +112,79 @@ |
242 | |
243 | The overview page leads back to the custom language codes overview. |
244 | |
245 | - >>> code_page = admin_browser.url |
246 | - >>> admin_browser.getLink("custom language codes overview").click() |
247 | - >>> admin_browser.url == custom_language_codes_page |
248 | + >>> code_page = owner_browser.url |
249 | + >>> owner_browser.getLink( |
250 | + ... "custom language codes overview").click() |
251 | + >>> owner_browser.url == custom_language_codes_page |
252 | True |
253 | |
254 | - >>> admin_browser.open(code_page) |
255 | + >>> owner_browser.open(code_page) |
256 | |
257 | -There is also a link for removing codes. The admin follows the link and |
258 | +There is also a link for removing codes. The owner follows the link and |
259 | removes the "no" custom language code. |
260 | |
261 | - >>> admin_browser.getLink("remove custom language code").click() |
262 | - >>> remove_page = admin_browser.url |
263 | - >>> admin_browser.getControl("Remove").click() |
264 | + >>> owner_browser.getLink("remove custom language code").click() |
265 | + >>> remove_page = owner_browser.url |
266 | + >>> owner_browser.getControl("Remove").click() |
267 | |
268 | This leads back to the overview page. |
269 | |
270 | - >>> admin_browser.url == custom_language_codes_page |
271 | + >>> owner_browser.url == custom_language_codes_page |
272 | True |
273 | |
274 | - >>> tag = find_tag_by_id(admin_browser.contents, 'empty') |
275 | - >>> print extract_text(tag.renderContents()) |
276 | - No custom language codes have been defined. |
277 | - |
278 | - |
279 | -Non-admin access |
280 | -================ |
281 | - |
282 | -A non-admin can see the page, actually, if they know the URL. This can |
283 | -be convenient for debugging. |
284 | - |
285 | - >>> owner_browser.open(custom_language_codes_page) |
286 | - |
287 | >>> tag = find_tag_by_id(owner_browser.contents, 'empty') |
288 | >>> print extract_text(tag.renderContents()) |
289 | No custom language codes have been defined. |
290 | |
291 | + |
292 | +Unprivileged access |
293 | +=================== |
294 | + |
295 | +A unprivileged user can see the page, actually, if they know the URL. |
296 | +This can be convenient for debugging. |
297 | + |
298 | + >>> user_browser.open(custom_language_codes_page) |
299 | + |
300 | + >>> tag = find_tag_by_id(user_browser.contents, 'empty') |
301 | + >>> print extract_text(tag.renderContents()) |
302 | + No custom language codes have been defined. |
303 | + |
304 | However all they get is a read-only version of the page. |
305 | |
306 | - >>> owner_browser.getLink("Add a custom language code").click() |
307 | + >>> user_browser.getLink("Add a custom language code").click() |
308 | Traceback (most recent call last): |
309 | ... |
310 | LinkNotFoundError |
311 | |
312 | The page for adding custom language codes is not accessible to them. |
313 | |
314 | + >>> user_browser.open(add_page) |
315 | + Traceback (most recent call last): |
316 | + ... |
317 | + Unauthorized: ... |
318 | + |
319 | +And naturally, if the owner creates a custom language code again, an |
320 | +unprivileged user can't remove it. |
321 | + |
322 | >>> owner_browser.open(add_page) |
323 | - Traceback (most recent call last): |
324 | - ... |
325 | - Unauthorized: ... |
326 | - |
327 | -And naturally, if an admin creates a custom language code again, a |
328 | -non-admin can't remove it. |
329 | - |
330 | - >>> admin_browser.open(add_page) |
331 | - >>> admin_browser.getControl("Language code:").value = 'no' |
332 | - >>> admin_browser.getControl("Language:").value = ['nn'] |
333 | - >>> admin_browser.getControl("Add").click() |
334 | - |
335 | - >>> owner_browser.open(custom_language_codes_page) |
336 | - >>> tag = find_tag_by_id(owner_browser.contents, 'nonempty') |
337 | + >>> owner_browser.getControl("Language code:").value = 'no' |
338 | + >>> owner_browser.getControl("Language:").value = ['nn'] |
339 | + >>> owner_browser.getControl("Add").click() |
340 | + |
341 | + >>> user_browser.open(custom_language_codes_page) |
342 | + >>> tag = find_tag_by_id(user_browser.contents, 'nonempty') |
343 | >>> print extract_text(tag.renderContents()) |
344 | Foo uses the following custom language codes: |
345 | Code... ...maps to language |
346 | no Norwegian Nynorsk |
347 | |
348 | - >>> owner_browser.getLink("no").click() |
349 | - >>> owner_browser.getLink("remove custom language code") |
350 | + >>> user_browser.getLink("no").click() |
351 | + >>> user_browser.getLink("remove custom language code") |
352 | Traceback (most recent call last): |
353 | ... |
354 | LinkNotFoundError |
355 | |
356 | - >>> owner_browser.open(remove_page) |
357 | + >>> user_browser.open(remove_page) |
358 | Traceback (most recent call last): |
359 | ... |
360 | Unauthorized: ... |
361 | @@ -188,7 +198,8 @@ |
362 | package--i.e. the combination of a distribution and a source package |
363 | name. However, since there is no Translations page for that type of |
364 | object (and we'd probably never go there if there were), the link is |
365 | -shown on the source package page. |
366 | +shown on the source package page. For distributions, the owner of the |
367 | +distribution's translation group is a translations administrator. |
368 | |
369 | >>> login(ANONYMOUS) |
370 | >>> from lp.registry.model.sourcepackage import SourcePackage |
371 | @@ -210,38 +221,44 @@ |
372 | ... distroseries=other_series, |
373 | ... sourcepackagename=package.sourcepackagename), |
374 | ... rootsite="translations") |
375 | + >>> translations_admin = factory.makePerson( |
376 | + ... email='ta@example.com', password='test') |
377 | + >>> translationgroup = factory.makeTranslationGroup( |
378 | + ... owner=translations_admin) |
379 | + >>> removeSecurityProxy(distro).translationgroup = translationgroup |
380 | >>> logout() |
381 | |
382 | - >>> admin_browser.open(package_page) |
383 | + >>> translations_browser = setupBrowser("Basic ta@example.com:test") |
384 | + >>> translations_browser.open(package_page) |
385 | |
386 | Of course in this case, the notice about there being no custom language |
387 | codes talks about a package, not a project. |
388 | |
389 | - >>> tag = find_custom_language_codes_link(admin_browser) |
390 | + >>> tag = find_custom_language_codes_link(translations_browser) |
391 | >>> print extract_text(tag.renderContents()) |
392 | If necessary, you may |
393 | define custom language codes |
394 | for this package. |
395 | |
396 | - >>> admin_browser.getLink("define custom language codes").click() |
397 | - >>> custom_language_codes_page = admin_browser.url |
398 | + >>> translations_browser.getLink("define custom language codes").click() |
399 | + >>> custom_language_codes_page = translations_browser.url |
400 | |
401 | - >>> tag = find_tag_by_id(admin_browser.contents, 'empty') |
402 | + >>> tag = find_tag_by_id(translations_browser.contents, 'empty') |
403 | >>> print extract_text(tag.renderContents()) |
404 | No custom language codes have been defined. |
405 | |
406 | -Again, an admin can add a language code. |
407 | - |
408 | - >>> admin_browser.getLink("Add a custom language code").click() |
409 | - >>> add_page = admin_browser.url |
410 | - |
411 | - >>> admin_browser.getControl("Language code:").value = 'pt-br' |
412 | - >>> admin_browser.getControl("Language:").value = ['pt_BR'] |
413 | - >>> admin_browser.getControl("Add").click() |
414 | +A translations admin can add a language code. |
415 | + |
416 | + >>> translations_browser.getLink("Add a custom language code").click() |
417 | + >>> add_page = translations_browser.url |
418 | + |
419 | + >>> translations_browser.getControl("Language code:").value = 'pt-br' |
420 | + >>> translations_browser.getControl("Language:").value = ['pt_BR'] |
421 | + >>> translations_browser.getControl("Add").click() |
422 | |
423 | The language code is displayed. |
424 | |
425 | - >>> tag = find_tag_by_id(admin_browser.contents, 'nonempty') |
426 | + >>> tag = find_tag_by_id(translations_browser.contents, 'nonempty') |
427 | >>> print extract_text(tag.renderContents()) |
428 | bar in distro uses the following custom language codes: |
429 | Code... ...maps to language |
430 | @@ -250,15 +267,15 @@ |
431 | It's also displayed identically on the same package but in another |
432 | release series of the same distribution. |
433 | |
434 | - >>> admin_browser.open(page_in_other_series) |
435 | - >>> tag = find_custom_language_codes_link(admin_browser) |
436 | + >>> translations_browser.open(page_in_other_series) |
437 | + >>> tag = find_custom_language_codes_link(translations_browser) |
438 | >>> print extract_text(tag.renderContents()) |
439 | If necessary, you may |
440 | define custom language codes |
441 | for this package. |
442 | |
443 | - >>> admin_browser.getLink("define custom language codes").click() |
444 | - >>> tag = find_tag_by_id(admin_browser.contents, 'nonempty') |
445 | + >>> translations_browser.getLink("define custom language codes").click() |
446 | + >>> tag = find_tag_by_id(translations_browser.contents, 'nonempty') |
447 | >>> print extract_text(tag.renderContents()) |
448 | bar in distro uses the following custom language codes: |
449 | Code... ...maps to language |
450 | @@ -267,13 +284,13 @@ |
451 | |
452 | The new code has a link there... |
453 | |
454 | - >>> admin_browser.getLink("pt-br").click() |
455 | + >>> translations_browser.getLink("pt-br").click() |
456 | |
457 | ...and can be deleted. |
458 | |
459 | - >>> admin_browser.getLink("remove custom language code").click() |
460 | - >>> admin_browser.getControl("Remove").click() |
461 | + >>> translations_browser.getLink("remove custom language code").click() |
462 | + >>> translations_browser.getControl("Remove").click() |
463 | |
464 | - >>> tag = find_tag_by_id(admin_browser.contents, 'empty') |
465 | + >>> tag = find_tag_by_id(translations_browser.contents, 'empty') |
466 | >>> print extract_text(tag.renderContents()) |
467 | No custom language codes have been defined. |
468 | |
469 | === modified file 'lib/lp/translations/templates/customlanguagecode-index.pt' |
470 | --- lib/lp/translations/templates/customlanguagecode-index.pt 2010-08-20 01:41:58 +0000 |
471 | +++ lib/lp/translations/templates/customlanguagecode-index.pt 2010-12-23 09:44:44 +0000 |
472 | @@ -26,7 +26,7 @@ |
473 | |
474 | <div class="portlet"> |
475 | <ul class="horizontal"> |
476 | - <li tal:condition="context/required:launchpad.Admin"> |
477 | + <li tal:condition="context/required:launchpad.TranslationsAdmin"> |
478 | <a class="remove sprite" |
479 | tal:attributes="href context/fmt:url:translations/+remove"> |
480 | remove custom language code |
481 | |
482 | === added file 'lib/lp/translations/templates/customlanguagecode-remove.pt' |
483 | --- lib/lp/translations/templates/customlanguagecode-remove.pt 1970-01-01 00:00:00 +0000 |
484 | +++ lib/lp/translations/templates/customlanguagecode-remove.pt 2010-12-23 09:44:44 +0000 |
485 | @@ -0,0 +1,18 @@ |
486 | +<html |
487 | + xmlns="http://www.w3.org/1999/xhtml" |
488 | + xmlns:tal="http://xml.zope.org/namespaces/tal" |
489 | + xmlns:metal="http://xml.zope.org/namespaces/metal" |
490 | + xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
491 | + metal:use-macro="view/macro:page/main_only" |
492 | + i18n:domain="launchpad"> |
493 | + <body> |
494 | + <div metal:fill-slot="main"> |
495 | + <div metal:use-macro="context/@@launchpad_form/form"> |
496 | + <div metal:fill-slot="extra_info" class="documentDescription"> |
497 | + You are going to remove the custom language code |
498 | + '<tal:language-code replace="view/code" />'. |
499 | + </div> |
500 | + </div> |
501 | + </div> |
502 | + </body> |
503 | +</html> |
504 | |
505 | === modified file 'lib/lp/translations/templates/customlanguagecodes-index.pt' |
506 | --- lib/lp/translations/templates/customlanguagecodes-index.pt 2010-08-20 01:41:58 +0000 |
507 | +++ lib/lp/translations/templates/customlanguagecodes-index.pt 2010-12-23 09:44:44 +0000 |
508 | @@ -33,7 +33,8 @@ |
509 | <tr> |
510 | <th>Code...</th> |
511 | <th>...maps to language</th> |
512 | - <th tal:condition="context/required:launchpad.Admin"></th> |
513 | + <th tal:condition="context/required:launchpad.TranslationsAdmin"> |
514 | + </th> |
515 | </tr> |
516 | </thead> |
517 | <tbody> |
518 | @@ -51,7 +52,7 @@ |
519 | — |
520 | </tal:nolanguage> |
521 | </td> |
522 | - <td tal:condition="context/required:launchpad.Admin"> |
523 | + <td tal:condition="context/required:launchpad.TranslationsAdmin"> |
524 | <a tal:attributes="href entry/fmt:url:translations/+remove" |
525 | alt="Remove" |
526 | title="Remove" |
527 | @@ -70,7 +71,7 @@ |
528 | |
529 | <div> |
530 | <a tal:attributes="href context/fmt:url:translations/+add-custom-language-code" |
531 | - tal:condition="context/required:launchpad.Admin" |
532 | + tal:condition="context/required:launchpad.TranslationsAdmin" |
533 | class="add sprite"> |
534 | Add a custom language code |
535 | </a> |
536 | |
537 | === modified file 'lib/lp/translations/templates/product-portlet-translatables.pt' |
538 | --- lib/lp/translations/templates/product-portlet-translatables.pt 2010-08-20 00:39:54 +0000 |
539 | +++ lib/lp/translations/templates/product-portlet-translatables.pt 2010-12-23 09:44:44 +0000 |
540 | @@ -65,7 +65,7 @@ |
541 | </div> |
542 | |
543 | <div class="portlet" |
544 | - tal:condition="context/required:launchpad.Admin" |
545 | + tal:condition="context/required:launchpad.TranslationsAdmin" |
546 | id="custom-language-codes"> |
547 | If necessary, you may |
548 | <a tal:attributes="href context/fmt:url:translations/+custom-language-codes" |
549 | |
550 | === modified file 'lib/lp/translations/templates/sourcepackage-translations.pt' |
551 | --- lib/lp/translations/templates/sourcepackage-translations.pt 2010-10-10 21:54:16 +0000 |
552 | +++ lib/lp/translations/templates/sourcepackage-translations.pt 2010-12-23 09:44:44 +0000 |
553 | @@ -39,7 +39,7 @@ |
554 | <a tal:attributes="href context/menu:navigation/download/url"> |
555 | download a full tarball</a> with translations. |
556 | </p> |
557 | - <p tal:condition="context/required:launchpad.Admin" |
558 | + <p tal:condition="context/distribution_sourcepackage/required:launchpad.TranslationsAdmin" |
559 | id="custom-language-codes"> |
560 | If necessary, you may |
561 | <a tal:attributes="href context/distribution_sourcepackage/fmt:url:translations/+custom-language-codes" |
Here is the incremental diff in relation to Adi's branch. This MP is only
about these changes.