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 | 185 | from lp.soyuz.interfaces.sourcepackagerelease import ISourcePackageRelease | 185 | from lp.soyuz.interfaces.sourcepackagerelease import ISourcePackageRelease |
6 | 186 | from lp.translations.interfaces.customlanguagecode import ( | 186 | from lp.translations.interfaces.customlanguagecode import ( |
7 | 187 | ICustomLanguageCode, | 187 | ICustomLanguageCode, |
8 | 188 | IHasCustomLanguageCodes, | ||
9 | 189 | ) | 188 | ) |
10 | 190 | from lp.translations.interfaces.languagepack import ILanguagePack | 189 | from lp.translations.interfaces.languagepack import ILanguagePack |
11 | 191 | from lp.translations.interfaces.pofile import IPOFile | 190 | from lp.translations.interfaces.pofile import IPOFile |
12 | @@ -744,6 +743,7 @@ | |||
13 | 744 | # with launchpad.View so that this adapter is used. For now, though, it's | 743 | # with launchpad.View so that this adapter is used. For now, though, it's |
14 | 745 | # going to be used only on the webservice (which explicitly checks for | 744 | # going to be used only on the webservice (which explicitly checks for |
15 | 746 | # launchpad.View) so that we don't leak memberships of private teams. | 745 | # launchpad.View) so that we don't leak memberships of private teams. |
16 | 746 | |||
17 | 747 | class ViewTeamMembership(AuthorizationBase): | 747 | class ViewTeamMembership(AuthorizationBase): |
18 | 748 | permission = 'launchpad.View' | 748 | permission = 'launchpad.View' |
19 | 749 | usedfor = ITeamMembership | 749 | usedfor = ITeamMembership |
20 | @@ -1728,27 +1728,23 @@ | |||
21 | 1728 | usedfor = ILanguage | 1728 | usedfor = ILanguage |
22 | 1729 | 1729 | ||
23 | 1730 | 1730 | ||
36 | 1731 | class AdminCustomLanguageCodes(OnlyRosettaExpertsAndAdmins): | 1731 | class AdminCustomLanguageCode(AuthorizationBase): |
25 | 1732 | """Controls administration of custom language codes. | ||
26 | 1733 | |||
27 | 1734 | Rosetta experts and Launchpad administrators can administer custom | ||
28 | 1735 | language codes. | ||
29 | 1736 | """ | ||
30 | 1737 | |||
31 | 1738 | permission = 'launchpad.TranslationsAdmin' | ||
32 | 1739 | usedfor = IHasCustomLanguageCodes | ||
33 | 1740 | |||
34 | 1741 | |||
35 | 1742 | class AdminCustomLanguageCode(OnlyRosettaExpertsAndAdmins): | ||
37 | 1743 | """Controls administration for a custom language code. | 1732 | """Controls administration for a custom language code. |
38 | 1744 | 1733 | ||
41 | 1745 | Rosetta experts and Launchpad administrators can administer a custom | 1734 | Whoever can admin a product's or distribution's translations can also |
42 | 1746 | language code. | 1735 | admin the custom language codes for it. |
43 | 1747 | """ | 1736 | """ |
44 | 1748 | |||
45 | 1749 | permission = 'launchpad.TranslationsAdmin' | 1737 | permission = 'launchpad.TranslationsAdmin' |
46 | 1750 | usedfor = ICustomLanguageCode | 1738 | usedfor = ICustomLanguageCode |
47 | 1751 | 1739 | ||
48 | 1740 | def checkAuthenticated(self, user): | ||
49 | 1741 | if self.obj.product is not None: | ||
50 | 1742 | return AdminProductTranslations( | ||
51 | 1743 | self.obj.product).checkAuthenticated(user) | ||
52 | 1744 | else: | ||
53 | 1745 | return AdminDistributionTranslations( | ||
54 | 1746 | self.obj.distribution).checkAuthenticated(user) | ||
55 | 1747 | |||
56 | 1752 | 1748 | ||
57 | 1753 | class AccessBranch(AuthorizationBase): | 1749 | class AccessBranch(AuthorizationBase): |
58 | 1754 | """Controls visibility of branches. | 1750 | """Controls visibility of branches. |
59 | @@ -1839,6 +1835,12 @@ | |||
60 | 1839 | self.obj.distribution).checkAuthenticated(user)) | 1835 | self.obj.distribution).checkAuthenticated(user)) |
61 | 1840 | 1836 | ||
62 | 1841 | 1837 | ||
63 | 1838 | class AdminDistributionSourcePackageTranslations( | ||
64 | 1839 | AdminDistroSeriesTranslations): | ||
65 | 1840 | """DistributionSourcePackage objects link to a distribution, too.""" | ||
66 | 1841 | usedfor = IDistributionSourcePackage | ||
67 | 1842 | |||
68 | 1843 | |||
69 | 1842 | class AdminProductSeriesTranslations(AuthorizationBase): | 1844 | class AdminProductSeriesTranslations(AuthorizationBase): |
70 | 1843 | permission = 'launchpad.TranslationsAdmin' | 1845 | permission = 'launchpad.TranslationsAdmin' |
71 | 1844 | usedfor = IProductSeries | 1846 | usedfor = IProductSeries |
72 | 1845 | 1847 | ||
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 | 941 | for="lp.registry.interfaces.distribution.IDistribution" | 941 | for="lp.registry.interfaces.distribution.IDistribution" |
78 | 942 | permission="zope.Public" | 942 | permission="zope.Public" |
79 | 943 | template="../templates/translations-portlet-not-using-launchpad-extra.pt" | 943 | template="../templates/translations-portlet-not-using-launchpad-extra.pt" |
81 | 944 | layer="lp.translations.publisher.TranslationsLayer"/> | 944 | layer="lp.translations.publisher.TranslationsLayer"/> |
82 | 945 | <browser:page | 945 | <browser:page |
83 | 946 | name="+portlet-configuration" | 946 | name="+portlet-configuration" |
84 | 947 | for="lp.registry.interfaces.distribution.IDistribution" | 947 | for="lp.registry.interfaces.distribution.IDistribution" |
85 | 948 | 948 | ||
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 | 46 | 46 | ||
91 | 47 | class CustomLanguageCodeBreadcrumb(Breadcrumb): | 47 | class CustomLanguageCodeBreadcrumb(Breadcrumb): |
92 | 48 | """Breadcrumb for a `CustomLanguageCode`.""" | 48 | """Breadcrumb for a `CustomLanguageCode`.""" |
93 | 49 | |||
94 | 49 | @property | 50 | @property |
95 | 50 | def text(self): | 51 | def text(self): |
96 | 51 | return smartquote( | 52 | return smartquote( |
97 | @@ -164,6 +165,7 @@ | |||
98 | 164 | class HasCustomLanguageCodesTraversalMixin: | 165 | class HasCustomLanguageCodesTraversalMixin: |
99 | 165 | """Navigate from an `IHasCustomLanguageCodes` to a `CustomLanguageCode`. | 166 | """Navigate from an `IHasCustomLanguageCodes` to a `CustomLanguageCode`. |
100 | 166 | """ | 167 | """ |
101 | 168 | |||
102 | 167 | @stepthrough('+customcode') | 169 | @stepthrough('+customcode') |
103 | 168 | def traverseCustomCode(self, name): | 170 | def traverseCustomCode(self, name): |
104 | 169 | """Traverse +customcode URLs.""" | 171 | """Traverse +customcode URLs.""" |
105 | 170 | 172 | ||
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 | 38 | >>> owner_browser = setupBrowser("Basic o@example.com:test") | 38 | >>> owner_browser = setupBrowser("Basic o@example.com:test") |
111 | 39 | >>> rosetta_admin_browser = setupRosettaExpertBrowser() | 39 | >>> rosetta_admin_browser = setupRosettaExpertBrowser() |
112 | 40 | 40 | ||
115 | 41 | A Launchpad administrator or Rosetta expert sees the link to the custom | 41 | The project's owner sees the link to the custom language codes on a project's |
116 | 42 | language codes on a project's main translations page. | 42 | main translations page. |
117 | 43 | 43 | ||
120 | 44 | >>> admin_browser.open(product_page) | 44 | >>> owner_browser.open(product_page) |
121 | 45 | >>> tag = find_custom_language_codes_link(admin_browser) | 45 | >>> tag = find_custom_language_codes_link(owner_browser) |
122 | 46 | >>> print extract_text(tag.renderContents()) | 46 | >>> print extract_text(tag.renderContents()) |
123 | 47 | If necessary, you may | 47 | If necessary, you may |
124 | 48 | define custom language codes | 48 | define custom language codes |
125 | 49 | for this project. | 49 | for this project. |
126 | 50 | 50 | ||
127 | 51 | Translation admins also have access to this link. | ||
128 | 52 | |||
129 | 51 | >>> rosetta_admin_browser.open(product_page) | 53 | >>> rosetta_admin_browser.open(product_page) |
130 | 52 | >>> tag = find_custom_language_codes_link(rosetta_admin_browser) | 54 | >>> tag = find_custom_language_codes_link(rosetta_admin_browser) |
131 | 53 | >>> print extract_text(tag.renderContents()) | 55 | >>> print extract_text(tag.renderContents()) |
132 | @@ -57,39 +59,37 @@ | |||
133 | 57 | 59 | ||
134 | 58 | The link goes to the custom language codes management page. | 60 | The link goes to the custom language codes management page. |
135 | 59 | 61 | ||
145 | 60 | >>> rosetta_admin_browser.getLink("define custom language codes").click() | 62 | >>> owner_browser.getLink("define custom language codes").click() |
146 | 61 | >>> custom_language_codes_page = rosetta_admin_browser.url | 63 | >>> custom_language_codes_page = owner_browser.url |
147 | 62 | 64 | ||
148 | 63 | Non-admins, even the project's owner, don't see this link. We do not | 65 | Other users don't see this link. |
149 | 64 | advertise this feature, since the proper solution is generally to use | 66 | |
150 | 65 | the right language codes. | 67 | >>> user_browser.open(product_page) |
151 | 66 | 68 | >>> print find_custom_language_codes_link(user_browser) | |
143 | 67 | >>> owner_browser.open(product_page) | ||
144 | 68 | >>> print find_custom_language_codes_link(owner_browser) | ||
152 | 69 | None | 69 | None |
153 | 70 | 70 | ||
154 | 71 | Initially the page shows no custom language codes for the project. | 71 | Initially the page shows no custom language codes for the project. |
155 | 72 | 72 | ||
157 | 73 | >>> tag = find_tag_by_id(rosetta_admin_browser.contents, 'empty') | 73 | >>> tag = find_tag_by_id(owner_browser.contents, 'empty') |
158 | 74 | >>> print extract_text(tag.renderContents()) | 74 | >>> print extract_text(tag.renderContents()) |
159 | 75 | No custom language codes have been defined. | 75 | No custom language codes have been defined. |
160 | 76 | 76 | ||
169 | 77 | The admin can add a custom language code. | 77 | There is a link to add a custom language code. |
170 | 78 | 78 | ||
171 | 79 | >>> rosetta_admin_browser.getLink("Add a custom language code").click() | 79 | >>> owner_browser.getLink("Add a custom language code").click() |
172 | 80 | >>> add_page = rosetta_admin_browser.url | 80 | >>> add_page = owner_browser.url |
173 | 81 | 81 | ||
174 | 82 | >>> rosetta_admin_browser.getControl("Language code:").value = 'no' | 82 | >>> owner_browser.getControl("Language code:").value = 'no' |
175 | 83 | >>> rosetta_admin_browser.getControl("Language:").value = ['nn'] | 83 | >>> owner_browser.getControl("Language:").value = ['nn'] |
176 | 84 | >>> rosetta_admin_browser.getControl("Add").click() | 84 | >>> owner_browser.getControl("Add").click() |
177 | 85 | 85 | ||
178 | 86 | This leads back to the custom language codes overview, where the new | 86 | This leads back to the custom language codes overview, where the new |
179 | 87 | code is now shown. | 87 | code is now shown. |
180 | 88 | 88 | ||
182 | 89 | >>> rosetta_admin_browser.url == custom_language_codes_page | 89 | >>> owner_browser.url == custom_language_codes_page |
183 | 90 | True | 90 | True |
184 | 91 | 91 | ||
186 | 92 | >>> tag = find_tag_by_id(rosetta_admin_browser.contents, 'nonempty') | 92 | >>> tag = find_tag_by_id(owner_browser.contents, 'nonempty') |
187 | 93 | >>> print extract_text(tag.renderContents()) | 93 | >>> print extract_text(tag.renderContents()) |
188 | 94 | Foo uses the following custom language codes: | 94 | Foo uses the following custom language codes: |
189 | 95 | Code... ...maps to language | 95 | Code... ...maps to language |
190 | @@ -98,8 +98,8 @@ | |||
191 | 98 | There is an overview page for the custom code, though there's not much | 98 | There is an overview page for the custom code, though there's not much |
192 | 99 | to see there. | 99 | to see there. |
193 | 100 | 100 | ||
196 | 101 | >>> rosetta_admin_browser.getLink("no").click() | 101 | >>> owner_browser.getLink("no").click() |
197 | 102 | >>> main = find_main_content(rosetta_admin_browser.contents) | 102 | >>> main = find_main_content(owner_browser.contents) |
198 | 103 | >>> print extract_text(main.renderContents()) | 103 | >>> print extract_text(main.renderContents()) |
199 | 104 | Foo Translations Custom language code ...no... | 104 | Foo Translations Custom language code ...no... |
200 | 105 | For Foo, uploads with the language code | 105 | For Foo, uploads with the language code |
201 | @@ -111,79 +111,79 @@ | |||
202 | 111 | 111 | ||
203 | 112 | The overview page leads back to the custom language codes overview. | 112 | The overview page leads back to the custom language codes overview. |
204 | 113 | 113 | ||
207 | 114 | >>> code_page = rosetta_admin_browser.url | 114 | >>> code_page = owner_browser.url |
208 | 115 | >>> rosetta_admin_browser.getLink( | 115 | >>> owner_browser.getLink( |
209 | 116 | ... "custom language codes overview").click() | 116 | ... "custom language codes overview").click() |
211 | 117 | >>> rosetta_admin_browser.url == custom_language_codes_page | 117 | >>> owner_browser.url == custom_language_codes_page |
212 | 118 | True | 118 | True |
213 | 119 | 119 | ||
215 | 120 | >>> rosetta_admin_browser.open(code_page) | 120 | >>> owner_browser.open(code_page) |
216 | 121 | 121 | ||
218 | 122 | There is also a link for removing codes. The admin follows the link and | 122 | There is also a link for removing codes. The owner follows the link and |
219 | 123 | removes the "no" custom language code. | 123 | removes the "no" custom language code. |
220 | 124 | 124 | ||
224 | 125 | >>> rosetta_admin_browser.getLink("remove custom language code").click() | 125 | >>> owner_browser.getLink("remove custom language code").click() |
225 | 126 | >>> remove_page = rosetta_admin_browser.url | 126 | >>> remove_page = owner_browser.url |
226 | 127 | >>> rosetta_admin_browser.getControl("Remove").click() | 127 | >>> owner_browser.getControl("Remove").click() |
227 | 128 | 128 | ||
228 | 129 | This leads back to the overview page. | 129 | This leads back to the overview page. |
229 | 130 | 130 | ||
231 | 131 | >>> rosetta_admin_browser.url == custom_language_codes_page | 131 | >>> owner_browser.url == custom_language_codes_page |
232 | 132 | True | 132 | True |
233 | 133 | 133 | ||
234 | 134 | >>> tag = find_tag_by_id(rosetta_admin_browser.contents, 'empty') | ||
235 | 135 | >>> print extract_text(tag.renderContents()) | ||
236 | 136 | No custom language codes have been defined. | ||
237 | 137 | |||
238 | 138 | |||
239 | 139 | Non-admin access | ||
240 | 140 | ================ | ||
241 | 141 | |||
242 | 142 | A non-admin can see the page, actually, if they know the URL. This can | ||
243 | 143 | be convenient for debugging. | ||
244 | 144 | |||
245 | 145 | >>> owner_browser.open(custom_language_codes_page) | ||
246 | 146 | |||
247 | 147 | >>> tag = find_tag_by_id(owner_browser.contents, 'empty') | 134 | >>> tag = find_tag_by_id(owner_browser.contents, 'empty') |
248 | 148 | >>> print extract_text(tag.renderContents()) | 135 | >>> print extract_text(tag.renderContents()) |
249 | 149 | No custom language codes have been defined. | 136 | No custom language codes have been defined. |
250 | 150 | 137 | ||
251 | 138 | |||
252 | 139 | Unprivileged access | ||
253 | 140 | =================== | ||
254 | 141 | |||
255 | 142 | A unprivileged user can see the page, actually, if they know the URL. | ||
256 | 143 | This can be convenient for debugging. | ||
257 | 144 | |||
258 | 145 | >>> user_browser.open(custom_language_codes_page) | ||
259 | 146 | |||
260 | 147 | >>> tag = find_tag_by_id(user_browser.contents, 'empty') | ||
261 | 148 | >>> print extract_text(tag.renderContents()) | ||
262 | 149 | No custom language codes have been defined. | ||
263 | 150 | |||
264 | 151 | However all they get is a read-only version of the page. | 151 | However all they get is a read-only version of the page. |
265 | 152 | 152 | ||
267 | 153 | >>> owner_browser.getLink("Add a custom language code").click() | 153 | >>> user_browser.getLink("Add a custom language code").click() |
268 | 154 | Traceback (most recent call last): | 154 | Traceback (most recent call last): |
269 | 155 | ... | 155 | ... |
270 | 156 | LinkNotFoundError | 156 | LinkNotFoundError |
271 | 157 | 157 | ||
272 | 158 | The page for adding custom language codes is not accessible to them. | 158 | The page for adding custom language codes is not accessible to them. |
273 | 159 | 159 | ||
274 | 160 | >>> user_browser.open(add_page) | ||
275 | 161 | Traceback (most recent call last): | ||
276 | 162 | ... | ||
277 | 163 | Unauthorized: ... | ||
278 | 164 | |||
279 | 165 | And naturally, if the owner creates a custom language code again, an | ||
280 | 166 | unprivileged user can't remove it. | ||
281 | 167 | |||
282 | 160 | >>> owner_browser.open(add_page) | 168 | >>> owner_browser.open(add_page) |
297 | 161 | Traceback (most recent call last): | 169 | >>> owner_browser.getControl("Language code:").value = 'no' |
298 | 162 | ... | 170 | >>> owner_browser.getControl("Language:").value = ['nn'] |
299 | 163 | Unauthorized: ... | 171 | >>> owner_browser.getControl("Add").click() |
300 | 164 | 172 | ||
301 | 165 | And naturally, if an admin creates a custom language code again, a | 173 | >>> user_browser.open(custom_language_codes_page) |
302 | 166 | non-admin can't remove it. | 174 | >>> tag = find_tag_by_id(user_browser.contents, 'nonempty') |
289 | 167 | |||
290 | 168 | >>> rosetta_admin_browser.open(add_page) | ||
291 | 169 | >>> rosetta_admin_browser.getControl("Language code:").value = 'no' | ||
292 | 170 | >>> rosetta_admin_browser.getControl("Language:").value = ['nn'] | ||
293 | 171 | >>> rosetta_admin_browser.getControl("Add").click() | ||
294 | 172 | |||
295 | 173 | >>> owner_browser.open(custom_language_codes_page) | ||
296 | 174 | >>> tag = find_tag_by_id(owner_browser.contents, 'nonempty') | ||
303 | 175 | >>> print extract_text(tag.renderContents()) | 175 | >>> print extract_text(tag.renderContents()) |
304 | 176 | Foo uses the following custom language codes: | 176 | Foo uses the following custom language codes: |
305 | 177 | Code... ...maps to language | 177 | Code... ...maps to language |
306 | 178 | no Norwegian Nynorsk | 178 | no Norwegian Nynorsk |
307 | 179 | 179 | ||
310 | 180 | >>> owner_browser.getLink("no").click() | 180 | >>> user_browser.getLink("no").click() |
311 | 181 | >>> owner_browser.getLink("remove custom language code") | 181 | >>> user_browser.getLink("remove custom language code") |
312 | 182 | Traceback (most recent call last): | 182 | Traceback (most recent call last): |
313 | 183 | ... | 183 | ... |
314 | 184 | LinkNotFoundError | 184 | LinkNotFoundError |
315 | 185 | 185 | ||
317 | 186 | >>> owner_browser.open(remove_page) | 186 | >>> user_browser.open(remove_page) |
318 | 187 | Traceback (most recent call last): | 187 | Traceback (most recent call last): |
319 | 188 | ... | 188 | ... |
320 | 189 | Unauthorized: ... | 189 | Unauthorized: ... |
321 | @@ -197,7 +197,8 @@ | |||
322 | 197 | package--i.e. the combination of a distribution and a source package | 197 | package--i.e. the combination of a distribution and a source package |
323 | 198 | name. However, since there is no Translations page for that type of | 198 | name. However, since there is no Translations page for that type of |
324 | 199 | object (and we'd probably never go there if there were), the link is | 199 | object (and we'd probably never go there if there were), the link is |
326 | 200 | shown on the source package page. | 200 | shown on the source package page. For distributions, the owner of the |
327 | 201 | distribution's translation group is a translations administrator. | ||
328 | 201 | 202 | ||
329 | 202 | >>> login(ANONYMOUS) | 203 | >>> login(ANONYMOUS) |
330 | 203 | >>> from lp.registry.model.sourcepackage import SourcePackage | 204 | >>> from lp.registry.model.sourcepackage import SourcePackage |
331 | @@ -219,38 +220,44 @@ | |||
332 | 219 | ... distroseries=other_series, | 220 | ... distroseries=other_series, |
333 | 220 | ... sourcepackagename=package.sourcepackagename), | 221 | ... sourcepackagename=package.sourcepackagename), |
334 | 221 | ... rootsite="translations") | 222 | ... rootsite="translations") |
335 | 223 | >>> translations_admin = factory.makePerson( | ||
336 | 224 | ... email='ta@example.com', password='test') | ||
337 | 225 | >>> translationgroup = factory.makeTranslationGroup( | ||
338 | 226 | ... owner=translations_admin) | ||
339 | 227 | >>> removeSecurityProxy(distro).translationgroup = translationgroup | ||
340 | 222 | >>> logout() | 228 | >>> logout() |
341 | 223 | 229 | ||
343 | 224 | >>> rosetta_admin_browser.open(package_page) | 230 | >>> translations_browser = setupBrowser("Basic ta@example.com:test") |
344 | 231 | >>> translations_browser.open(package_page) | ||
345 | 225 | 232 | ||
346 | 226 | Of course in this case, the notice about there being no custom language | 233 | Of course in this case, the notice about there being no custom language |
347 | 227 | codes talks about a package, not a project. | 234 | codes talks about a package, not a project. |
348 | 228 | 235 | ||
350 | 229 | >>> tag = find_custom_language_codes_link(rosetta_admin_browser) | 236 | >>> tag = find_custom_language_codes_link(translations_browser) |
351 | 230 | >>> print extract_text(tag.renderContents()) | 237 | >>> print extract_text(tag.renderContents()) |
352 | 231 | If necessary, you may | 238 | If necessary, you may |
353 | 232 | define custom language codes | 239 | define custom language codes |
354 | 233 | for this package. | 240 | for this package. |
355 | 234 | 241 | ||
358 | 235 | >>> rosetta_admin_browser.getLink("define custom language codes").click() | 242 | >>> translations_browser.getLink("define custom language codes").click() |
359 | 236 | >>> custom_language_codes_page = rosetta_admin_browser.url | 243 | >>> custom_language_codes_page = translations_browser.url |
360 | 237 | 244 | ||
362 | 238 | >>> tag = find_tag_by_id(rosetta_admin_browser.contents, 'empty') | 245 | >>> tag = find_tag_by_id(translations_browser.contents, 'empty') |
363 | 239 | >>> print extract_text(tag.renderContents()) | 246 | >>> print extract_text(tag.renderContents()) |
364 | 240 | No custom language codes have been defined. | 247 | No custom language codes have been defined. |
365 | 241 | 248 | ||
374 | 242 | Again, an admin can add a language code. | 249 | A translations admin can add a language code. |
375 | 243 | 250 | ||
376 | 244 | >>> rosetta_admin_browser.getLink("Add a custom language code").click() | 251 | >>> translations_browser.getLink("Add a custom language code").click() |
377 | 245 | >>> add_page = rosetta_admin_browser.url | 252 | >>> add_page = translations_browser.url |
378 | 246 | 253 | ||
379 | 247 | >>> rosetta_admin_browser.getControl("Language code:").value = 'pt-br' | 254 | >>> translations_browser.getControl("Language code:").value = 'pt-br' |
380 | 248 | >>> rosetta_admin_browser.getControl("Language:").value = ['pt_BR'] | 255 | >>> translations_browser.getControl("Language:").value = ['pt_BR'] |
381 | 249 | >>> rosetta_admin_browser.getControl("Add").click() | 256 | >>> translations_browser.getControl("Add").click() |
382 | 250 | 257 | ||
383 | 251 | The language code is displayed. | 258 | The language code is displayed. |
384 | 252 | 259 | ||
386 | 253 | >>> tag = find_tag_by_id(rosetta_admin_browser.contents, 'nonempty') | 260 | >>> tag = find_tag_by_id(translations_browser.contents, 'nonempty') |
387 | 254 | >>> print extract_text(tag.renderContents()) | 261 | >>> print extract_text(tag.renderContents()) |
388 | 255 | bar in distro uses the following custom language codes: | 262 | bar in distro uses the following custom language codes: |
389 | 256 | Code... ...maps to language | 263 | Code... ...maps to language |
390 | @@ -259,15 +266,15 @@ | |||
391 | 259 | It's also displayed identically on the same package but in another | 266 | It's also displayed identically on the same package but in another |
392 | 260 | release series of the same distribution. | 267 | release series of the same distribution. |
393 | 261 | 268 | ||
396 | 262 | >>> rosetta_admin_browser.open(page_in_other_series) | 269 | >>> translations_browser.open(page_in_other_series) |
397 | 263 | >>> tag = find_custom_language_codes_link(rosetta_admin_browser) | 270 | >>> tag = find_custom_language_codes_link(translations_browser) |
398 | 264 | >>> print extract_text(tag.renderContents()) | 271 | >>> print extract_text(tag.renderContents()) |
399 | 265 | If necessary, you may | 272 | If necessary, you may |
400 | 266 | define custom language codes | 273 | define custom language codes |
401 | 267 | for this package. | 274 | for this package. |
402 | 268 | 275 | ||
405 | 269 | >>> rosetta_admin_browser.getLink("define custom language codes").click() | 276 | >>> translations_browser.getLink("define custom language codes").click() |
406 | 270 | >>> tag = find_tag_by_id(rosetta_admin_browser.contents, 'nonempty') | 277 | >>> tag = find_tag_by_id(translations_browser.contents, 'nonempty') |
407 | 271 | >>> print extract_text(tag.renderContents()) | 278 | >>> print extract_text(tag.renderContents()) |
408 | 272 | bar in distro uses the following custom language codes: | 279 | bar in distro uses the following custom language codes: |
409 | 273 | Code... ...maps to language | 280 | Code... ...maps to language |
410 | @@ -276,13 +283,13 @@ | |||
411 | 276 | 283 | ||
412 | 277 | The new code has a link there... | 284 | The new code has a link there... |
413 | 278 | 285 | ||
415 | 279 | >>> rosetta_admin_browser.getLink("pt-br").click() | 286 | >>> translations_browser.getLink("pt-br").click() |
416 | 280 | 287 | ||
417 | 281 | ...and can be deleted. | 288 | ...and can be deleted. |
418 | 282 | 289 | ||
421 | 283 | >>> rosetta_admin_browser.getLink("remove custom language code").click() | 290 | >>> translations_browser.getLink("remove custom language code").click() |
422 | 284 | >>> rosetta_admin_browser.getControl("Remove").click() | 291 | >>> translations_browser.getControl("Remove").click() |
423 | 285 | 292 | ||
425 | 286 | >>> tag = find_tag_by_id(rosetta_admin_browser.contents, 'empty') | 293 | >>> tag = find_tag_by_id(translations_browser.contents, 'empty') |
426 | 287 | >>> print extract_text(tag.renderContents()) | 294 | >>> print extract_text(tag.renderContents()) |
427 | 288 | No custom language codes have been defined. | 295 | 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 | 183 | IPackageUploadQueue, | 183 | IPackageUploadQueue, |
6 | 184 | ) | 184 | ) |
7 | 185 | from lp.soyuz.interfaces.sourcepackagerelease import ISourcePackageRelease | 185 | from lp.soyuz.interfaces.sourcepackagerelease import ISourcePackageRelease |
8 | 186 | from lp.translations.interfaces.customlanguagecode import ( | ||
9 | 187 | ICustomLanguageCode, | ||
10 | 188 | ) | ||
11 | 186 | from lp.translations.interfaces.languagepack import ILanguagePack | 189 | from lp.translations.interfaces.languagepack import ILanguagePack |
12 | 187 | from lp.translations.interfaces.pofile import IPOFile | 190 | from lp.translations.interfaces.pofile import IPOFile |
13 | 188 | from lp.translations.interfaces.potemplate import IPOTemplate | 191 | from lp.translations.interfaces.potemplate import IPOTemplate |
14 | @@ -740,6 +743,7 @@ | |||
15 | 740 | # with launchpad.View so that this adapter is used. For now, though, it's | 743 | # with launchpad.View so that this adapter is used. For now, though, it's |
16 | 741 | # going to be used only on the webservice (which explicitly checks for | 744 | # going to be used only on the webservice (which explicitly checks for |
17 | 742 | # launchpad.View) so that we don't leak memberships of private teams. | 745 | # launchpad.View) so that we don't leak memberships of private teams. |
18 | 746 | |||
19 | 743 | class ViewTeamMembership(AuthorizationBase): | 747 | class ViewTeamMembership(AuthorizationBase): |
20 | 744 | permission = 'launchpad.View' | 748 | permission = 'launchpad.View' |
21 | 745 | usedfor = ITeamMembership | 749 | usedfor = ITeamMembership |
22 | @@ -1724,6 +1728,24 @@ | |||
23 | 1724 | usedfor = ILanguage | 1728 | usedfor = ILanguage |
24 | 1725 | 1729 | ||
25 | 1726 | 1730 | ||
26 | 1731 | class AdminCustomLanguageCode(AuthorizationBase): | ||
27 | 1732 | """Controls administration for a custom language code. | ||
28 | 1733 | |||
29 | 1734 | Whoever can admin a product's or distribution's translations can also | ||
30 | 1735 | admin the custom language codes for it. | ||
31 | 1736 | """ | ||
32 | 1737 | permission = 'launchpad.TranslationsAdmin' | ||
33 | 1738 | usedfor = ICustomLanguageCode | ||
34 | 1739 | |||
35 | 1740 | def checkAuthenticated(self, user): | ||
36 | 1741 | if self.obj.product is not None: | ||
37 | 1742 | return AdminProductTranslations( | ||
38 | 1743 | self.obj.product).checkAuthenticated(user) | ||
39 | 1744 | else: | ||
40 | 1745 | return AdminDistributionTranslations( | ||
41 | 1746 | self.obj.distribution).checkAuthenticated(user) | ||
42 | 1747 | |||
43 | 1748 | |||
44 | 1727 | class AccessBranch(AuthorizationBase): | 1749 | class AccessBranch(AuthorizationBase): |
45 | 1728 | """Controls visibility of branches. | 1750 | """Controls visibility of branches. |
46 | 1729 | 1751 | ||
47 | @@ -1813,6 +1835,12 @@ | |||
48 | 1813 | self.obj.distribution).checkAuthenticated(user)) | 1835 | self.obj.distribution).checkAuthenticated(user)) |
49 | 1814 | 1836 | ||
50 | 1815 | 1837 | ||
51 | 1838 | class AdminDistributionSourcePackageTranslations( | ||
52 | 1839 | AdminDistroSeriesTranslations): | ||
53 | 1840 | """DistributionSourcePackage objects link to a distribution, too.""" | ||
54 | 1841 | usedfor = IDistributionSourcePackage | ||
55 | 1842 | |||
56 | 1843 | |||
57 | 1816 | class AdminProductSeriesTranslations(AuthorizationBase): | 1844 | class AdminProductSeriesTranslations(AuthorizationBase): |
58 | 1817 | permission = 'launchpad.TranslationsAdmin' | 1845 | permission = 'launchpad.TranslationsAdmin' |
59 | 1818 | usedfor = IProductSeries | 1846 | usedfor = IProductSeries |
60 | 1819 | 1847 | ||
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 | 941 | for="lp.registry.interfaces.distribution.IDistribution" | 941 | for="lp.registry.interfaces.distribution.IDistribution" |
66 | 942 | permission="zope.Public" | 942 | permission="zope.Public" |
67 | 943 | template="../templates/translations-portlet-not-using-launchpad-extra.pt" | 943 | template="../templates/translations-portlet-not-using-launchpad-extra.pt" |
69 | 944 | layer="lp.translations.publisher.TranslationsLayer"/> | 944 | layer="lp.translations.publisher.TranslationsLayer"/> |
70 | 945 | <browser:page | 945 | <browser:page |
71 | 946 | name="+portlet-configuration" | 946 | name="+portlet-configuration" |
72 | 947 | for="lp.registry.interfaces.distribution.IDistribution" | 947 | for="lp.registry.interfaces.distribution.IDistribution" |
73 | @@ -1042,9 +1042,9 @@ | |||
74 | 1042 | <browser:page | 1042 | <browser:page |
75 | 1043 | name="+remove" | 1043 | name="+remove" |
76 | 1044 | for="lp.translations.interfaces.customlanguagecode.ICustomLanguageCode" | 1044 | for="lp.translations.interfaces.customlanguagecode.ICustomLanguageCode" |
78 | 1045 | permission="launchpad.Admin" | 1045 | permission="launchpad.TranslationsAdmin" |
79 | 1046 | class="lp.translations.browser.customlanguagecode.CustomLanguageCodeRemoveView" | 1046 | class="lp.translations.browser.customlanguagecode.CustomLanguageCodeRemoveView" |
81 | 1047 | template="../../app/templates/generic-edit.pt" | 1047 | template="../templates/customlanguagecode-remove.pt" |
82 | 1048 | layer="lp.translations.publisher.TranslationsLayer"/> | 1048 | layer="lp.translations.publisher.TranslationsLayer"/> |
83 | 1049 | 1049 | ||
84 | 1050 | <!-- IHasCustomLanguageCodes --> | 1050 | <!-- IHasCustomLanguageCodes --> |
85 | @@ -1062,7 +1062,7 @@ | |||
86 | 1062 | layer="lp.translations.publisher.TranslationsLayer" | 1062 | layer="lp.translations.publisher.TranslationsLayer" |
87 | 1063 | class="lp.translations.browser.customlanguagecode.CustomLanguageCodeAddView" | 1063 | class="lp.translations.browser.customlanguagecode.CustomLanguageCodeAddView" |
88 | 1064 | template="../templates/customlanguagecode-add.pt" | 1064 | template="../templates/customlanguagecode-add.pt" |
90 | 1065 | permission="launchpad.Admin"/> | 1065 | permission="launchpad.TranslationsAdmin"/> |
91 | 1066 | 1066 | ||
92 | 1067 | </facet> | 1067 | </facet> |
93 | 1068 | 1068 | ||
94 | 1069 | 1069 | ||
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 | 11 | 'CustomLanguageCodeView', | 11 | 'CustomLanguageCodeView', |
100 | 12 | 'HasCustomLanguageCodesNavigation', | 12 | 'HasCustomLanguageCodesNavigation', |
101 | 13 | 'HasCustomLanguageCodesTraversalMixin', | 13 | 'HasCustomLanguageCodesTraversalMixin', |
103 | 14 | ] | 14 | ] |
104 | 15 | 15 | ||
105 | 16 | 16 | ||
106 | 17 | import re | 17 | import re |
107 | @@ -46,6 +46,7 @@ | |||
108 | 46 | 46 | ||
109 | 47 | class CustomLanguageCodeBreadcrumb(Breadcrumb): | 47 | class CustomLanguageCodeBreadcrumb(Breadcrumb): |
110 | 48 | """Breadcrumb for a `CustomLanguageCode`.""" | 48 | """Breadcrumb for a `CustomLanguageCode`.""" |
111 | 49 | |||
112 | 49 | @property | 50 | @property |
113 | 50 | def text(self): | 51 | def text(self): |
114 | 51 | return smartquote( | 52 | return smartquote( |
115 | @@ -126,6 +127,13 @@ | |||
116 | 126 | class CustomLanguageCodeView(LaunchpadView): | 127 | class CustomLanguageCodeView(LaunchpadView): |
117 | 127 | schema = ICustomLanguageCode | 128 | schema = ICustomLanguageCode |
118 | 128 | 129 | ||
119 | 130 | @property | ||
120 | 131 | def label(self): | ||
121 | 132 | target_displayname = self.context.translation_target.displayname | ||
122 | 133 | return smartquote( | ||
123 | 134 | 'Custom language code "%s" for %s' % ( | ||
124 | 135 | self.context.language_code, target_displayname)) | ||
125 | 136 | |||
126 | 129 | 137 | ||
127 | 130 | class CustomLanguageCodeRemoveView(LaunchpadFormView): | 138 | class CustomLanguageCodeRemoveView(LaunchpadFormView): |
128 | 131 | """View for removing a `CustomLanguageCode`.""" | 139 | """View for removing a `CustomLanguageCode`.""" |
129 | @@ -164,6 +172,7 @@ | |||
130 | 164 | class HasCustomLanguageCodesTraversalMixin: | 172 | class HasCustomLanguageCodesTraversalMixin: |
131 | 165 | """Navigate from an `IHasCustomLanguageCodes` to a `CustomLanguageCode`. | 173 | """Navigate from an `IHasCustomLanguageCodes` to a `CustomLanguageCode`. |
132 | 166 | """ | 174 | """ |
133 | 175 | |||
134 | 167 | @stepthrough('+customcode') | 176 | @stepthrough('+customcode') |
135 | 168 | def traverseCustomCode(self, name): | 177 | def traverseCustomCode(self, name): |
136 | 169 | """Traverse +customcode URLs.""" | 178 | """Traverse +customcode URLs.""" |
137 | 170 | 179 | ||
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 | 36 | >>> logout() | 36 | >>> logout() |
143 | 37 | 37 | ||
144 | 38 | >>> owner_browser = setupBrowser("Basic o@example.com:test") | 38 | >>> owner_browser = setupBrowser("Basic o@example.com:test") |
151 | 39 | 39 | >>> rosetta_admin_browser = setupRosettaExpertBrowser() | |
152 | 40 | An administrator sees the link to the custom language codes on a | 40 | |
153 | 41 | project's main translations page. | 41 | The project's owner sees the link to the custom language codes on a project's |
154 | 42 | 42 | main translations page. | |
155 | 43 | >>> admin_browser.open(product_page) | 43 | |
156 | 44 | >>> tag = find_custom_language_codes_link(admin_browser) | 44 | >>> owner_browser.open(product_page) |
157 | 45 | >>> tag = find_custom_language_codes_link(owner_browser) | ||
158 | 46 | >>> print extract_text(tag.renderContents()) | ||
159 | 47 | If necessary, you may | ||
160 | 48 | define custom language codes | ||
161 | 49 | for this project. | ||
162 | 50 | |||
163 | 51 | Translation admins also have access to this link. | ||
164 | 52 | |||
165 | 53 | >>> rosetta_admin_browser.open(product_page) | ||
166 | 54 | >>> tag = find_custom_language_codes_link(rosetta_admin_browser) | ||
167 | 45 | >>> print extract_text(tag.renderContents()) | 55 | >>> print extract_text(tag.renderContents()) |
168 | 46 | If necessary, you may | 56 | If necessary, you may |
169 | 47 | define custom language codes | 57 | define custom language codes |
170 | @@ -49,39 +59,37 @@ | |||
171 | 49 | 59 | ||
172 | 50 | The link goes to the custom language codes management page. | 60 | The link goes to the custom language codes management page. |
173 | 51 | 61 | ||
183 | 52 | >>> admin_browser.getLink("define custom language codes").click() | 62 | >>> owner_browser.getLink("define custom language codes").click() |
184 | 53 | >>> custom_language_codes_page = admin_browser.url | 63 | >>> custom_language_codes_page = owner_browser.url |
185 | 54 | 64 | ||
186 | 55 | Non-admins, even the project's owner, don't see this link. We do not | 65 | Other users don't see this link. |
187 | 56 | advertise this feature, since the proper solution is generally to use | 66 | |
188 | 57 | the right language codes. | 67 | >>> user_browser.open(product_page) |
189 | 58 | 68 | >>> print find_custom_language_codes_link(user_browser) | |
181 | 59 | >>> owner_browser.open(product_page) | ||
182 | 60 | >>> print find_custom_language_codes_link(owner_browser) | ||
190 | 61 | None | 69 | None |
191 | 62 | 70 | ||
192 | 63 | Initially the page shows no custom language codes for the project. | 71 | Initially the page shows no custom language codes for the project. |
193 | 64 | 72 | ||
195 | 65 | >>> tag = find_tag_by_id(admin_browser.contents, 'empty') | 73 | >>> tag = find_tag_by_id(owner_browser.contents, 'empty') |
196 | 66 | >>> print extract_text(tag.renderContents()) | 74 | >>> print extract_text(tag.renderContents()) |
197 | 67 | No custom language codes have been defined. | 75 | No custom language codes have been defined. |
198 | 68 | 76 | ||
207 | 69 | The admin can add a custom language code. | 77 | There is a link to add a custom language code. |
208 | 70 | 78 | ||
209 | 71 | >>> admin_browser.getLink("Add a custom language code").click() | 79 | >>> owner_browser.getLink("Add a custom language code").click() |
210 | 72 | >>> add_page = admin_browser.url | 80 | >>> add_page = owner_browser.url |
211 | 73 | 81 | ||
212 | 74 | >>> admin_browser.getControl("Language code:").value = 'no' | 82 | >>> owner_browser.getControl("Language code:").value = 'no' |
213 | 75 | >>> admin_browser.getControl("Language:").value = ['nn'] | 83 | >>> owner_browser.getControl("Language:").value = ['nn'] |
214 | 76 | >>> admin_browser.getControl("Add").click() | 84 | >>> owner_browser.getControl("Add").click() |
215 | 77 | 85 | ||
216 | 78 | This leads back to the custom language codes overview, where the new | 86 | This leads back to the custom language codes overview, where the new |
217 | 79 | code is now shown. | 87 | code is now shown. |
218 | 80 | 88 | ||
220 | 81 | >>> admin_browser.url == custom_language_codes_page | 89 | >>> owner_browser.url == custom_language_codes_page |
221 | 82 | True | 90 | True |
222 | 83 | 91 | ||
224 | 84 | >>> tag = find_tag_by_id(admin_browser.contents, 'nonempty') | 92 | >>> tag = find_tag_by_id(owner_browser.contents, 'nonempty') |
225 | 85 | >>> print extract_text(tag.renderContents()) | 93 | >>> print extract_text(tag.renderContents()) |
226 | 86 | Foo uses the following custom language codes: | 94 | Foo uses the following custom language codes: |
227 | 87 | Code... ...maps to language | 95 | Code... ...maps to language |
228 | @@ -90,9 +98,10 @@ | |||
229 | 90 | There is an overview page for the custom code, though there's not much | 98 | There is an overview page for the custom code, though there's not much |
230 | 91 | to see there. | 99 | to see there. |
231 | 92 | 100 | ||
234 | 93 | >>> admin_browser.getLink("no").click() | 101 | >>> owner_browser.getLink("no").click() |
235 | 94 | >>> main = find_main_content(admin_browser.contents) | 102 | >>> main = find_main_content(owner_browser.contents) |
236 | 95 | >>> print extract_text(main.renderContents()) | 103 | >>> print extract_text(main.renderContents()) |
237 | 104 | Custom language code ...no... for Foo | ||
238 | 96 | Foo Translations Custom language code ...no... | 105 | Foo Translations Custom language code ...no... |
239 | 97 | For Foo, uploads with the language code | 106 | For Foo, uploads with the language code |
240 | 98 | “no” | 107 | “no” |
241 | @@ -103,78 +112,79 @@ | |||
242 | 103 | 112 | ||
243 | 104 | The overview page leads back to the custom language codes overview. | 113 | The overview page leads back to the custom language codes overview. |
244 | 105 | 114 | ||
248 | 106 | >>> code_page = admin_browser.url | 115 | >>> code_page = owner_browser.url |
249 | 107 | >>> admin_browser.getLink("custom language codes overview").click() | 116 | >>> owner_browser.getLink( |
250 | 108 | >>> admin_browser.url == custom_language_codes_page | 117 | ... "custom language codes overview").click() |
251 | 118 | >>> owner_browser.url == custom_language_codes_page | ||
252 | 109 | True | 119 | True |
253 | 110 | 120 | ||
255 | 111 | >>> admin_browser.open(code_page) | 121 | >>> owner_browser.open(code_page) |
256 | 112 | 122 | ||
258 | 113 | There is also a link for removing codes. The admin follows the link and | 123 | There is also a link for removing codes. The owner follows the link and |
259 | 114 | removes the "no" custom language code. | 124 | removes the "no" custom language code. |
260 | 115 | 125 | ||
264 | 116 | >>> admin_browser.getLink("remove custom language code").click() | 126 | >>> owner_browser.getLink("remove custom language code").click() |
265 | 117 | >>> remove_page = admin_browser.url | 127 | >>> remove_page = owner_browser.url |
266 | 118 | >>> admin_browser.getControl("Remove").click() | 128 | >>> owner_browser.getControl("Remove").click() |
267 | 119 | 129 | ||
268 | 120 | This leads back to the overview page. | 130 | This leads back to the overview page. |
269 | 121 | 131 | ||
271 | 122 | >>> admin_browser.url == custom_language_codes_page | 132 | >>> owner_browser.url == custom_language_codes_page |
272 | 123 | True | 133 | True |
273 | 124 | 134 | ||
274 | 125 | >>> tag = find_tag_by_id(admin_browser.contents, 'empty') | ||
275 | 126 | >>> print extract_text(tag.renderContents()) | ||
276 | 127 | No custom language codes have been defined. | ||
277 | 128 | |||
278 | 129 | |||
279 | 130 | Non-admin access | ||
280 | 131 | ================ | ||
281 | 132 | |||
282 | 133 | A non-admin can see the page, actually, if they know the URL. This can | ||
283 | 134 | be convenient for debugging. | ||
284 | 135 | |||
285 | 136 | >>> owner_browser.open(custom_language_codes_page) | ||
286 | 137 | |||
287 | 138 | >>> tag = find_tag_by_id(owner_browser.contents, 'empty') | 135 | >>> tag = find_tag_by_id(owner_browser.contents, 'empty') |
288 | 139 | >>> print extract_text(tag.renderContents()) | 136 | >>> print extract_text(tag.renderContents()) |
289 | 140 | No custom language codes have been defined. | 137 | No custom language codes have been defined. |
290 | 141 | 138 | ||
291 | 139 | |||
292 | 140 | Unprivileged access | ||
293 | 141 | =================== | ||
294 | 142 | |||
295 | 143 | A unprivileged user can see the page, actually, if they know the URL. | ||
296 | 144 | This can be convenient for debugging. | ||
297 | 145 | |||
298 | 146 | >>> user_browser.open(custom_language_codes_page) | ||
299 | 147 | |||
300 | 148 | >>> tag = find_tag_by_id(user_browser.contents, 'empty') | ||
301 | 149 | >>> print extract_text(tag.renderContents()) | ||
302 | 150 | No custom language codes have been defined. | ||
303 | 151 | |||
304 | 142 | However all they get is a read-only version of the page. | 152 | However all they get is a read-only version of the page. |
305 | 143 | 153 | ||
307 | 144 | >>> owner_browser.getLink("Add a custom language code").click() | 154 | >>> user_browser.getLink("Add a custom language code").click() |
308 | 145 | Traceback (most recent call last): | 155 | Traceback (most recent call last): |
309 | 146 | ... | 156 | ... |
310 | 147 | LinkNotFoundError | 157 | LinkNotFoundError |
311 | 148 | 158 | ||
312 | 149 | The page for adding custom language codes is not accessible to them. | 159 | The page for adding custom language codes is not accessible to them. |
313 | 150 | 160 | ||
314 | 161 | >>> user_browser.open(add_page) | ||
315 | 162 | Traceback (most recent call last): | ||
316 | 163 | ... | ||
317 | 164 | Unauthorized: ... | ||
318 | 165 | |||
319 | 166 | And naturally, if the owner creates a custom language code again, an | ||
320 | 167 | unprivileged user can't remove it. | ||
321 | 168 | |||
322 | 151 | >>> owner_browser.open(add_page) | 169 | >>> owner_browser.open(add_page) |
337 | 152 | Traceback (most recent call last): | 170 | >>> owner_browser.getControl("Language code:").value = 'no' |
338 | 153 | ... | 171 | >>> owner_browser.getControl("Language:").value = ['nn'] |
339 | 154 | Unauthorized: ... | 172 | >>> owner_browser.getControl("Add").click() |
340 | 155 | 173 | ||
341 | 156 | And naturally, if an admin creates a custom language code again, a | 174 | >>> user_browser.open(custom_language_codes_page) |
342 | 157 | non-admin can't remove it. | 175 | >>> tag = find_tag_by_id(user_browser.contents, 'nonempty') |
329 | 158 | |||
330 | 159 | >>> admin_browser.open(add_page) | ||
331 | 160 | >>> admin_browser.getControl("Language code:").value = 'no' | ||
332 | 161 | >>> admin_browser.getControl("Language:").value = ['nn'] | ||
333 | 162 | >>> admin_browser.getControl("Add").click() | ||
334 | 163 | |||
335 | 164 | >>> owner_browser.open(custom_language_codes_page) | ||
336 | 165 | >>> tag = find_tag_by_id(owner_browser.contents, 'nonempty') | ||
343 | 166 | >>> print extract_text(tag.renderContents()) | 176 | >>> print extract_text(tag.renderContents()) |
344 | 167 | Foo uses the following custom language codes: | 177 | Foo uses the following custom language codes: |
345 | 168 | Code... ...maps to language | 178 | Code... ...maps to language |
346 | 169 | no Norwegian Nynorsk | 179 | no Norwegian Nynorsk |
347 | 170 | 180 | ||
350 | 171 | >>> owner_browser.getLink("no").click() | 181 | >>> user_browser.getLink("no").click() |
351 | 172 | >>> owner_browser.getLink("remove custom language code") | 182 | >>> user_browser.getLink("remove custom language code") |
352 | 173 | Traceback (most recent call last): | 183 | Traceback (most recent call last): |
353 | 174 | ... | 184 | ... |
354 | 175 | LinkNotFoundError | 185 | LinkNotFoundError |
355 | 176 | 186 | ||
357 | 177 | >>> owner_browser.open(remove_page) | 187 | >>> user_browser.open(remove_page) |
358 | 178 | Traceback (most recent call last): | 188 | Traceback (most recent call last): |
359 | 179 | ... | 189 | ... |
360 | 180 | Unauthorized: ... | 190 | Unauthorized: ... |
361 | @@ -188,7 +198,8 @@ | |||
362 | 188 | package--i.e. the combination of a distribution and a source package | 198 | package--i.e. the combination of a distribution and a source package |
363 | 189 | name. However, since there is no Translations page for that type of | 199 | name. However, since there is no Translations page for that type of |
364 | 190 | object (and we'd probably never go there if there were), the link is | 200 | object (and we'd probably never go there if there were), the link is |
366 | 191 | shown on the source package page. | 201 | shown on the source package page. For distributions, the owner of the |
367 | 202 | distribution's translation group is a translations administrator. | ||
368 | 192 | 203 | ||
369 | 193 | >>> login(ANONYMOUS) | 204 | >>> login(ANONYMOUS) |
370 | 194 | >>> from lp.registry.model.sourcepackage import SourcePackage | 205 | >>> from lp.registry.model.sourcepackage import SourcePackage |
371 | @@ -210,38 +221,44 @@ | |||
372 | 210 | ... distroseries=other_series, | 221 | ... distroseries=other_series, |
373 | 211 | ... sourcepackagename=package.sourcepackagename), | 222 | ... sourcepackagename=package.sourcepackagename), |
374 | 212 | ... rootsite="translations") | 223 | ... rootsite="translations") |
375 | 224 | >>> translations_admin = factory.makePerson( | ||
376 | 225 | ... email='ta@example.com', password='test') | ||
377 | 226 | >>> translationgroup = factory.makeTranslationGroup( | ||
378 | 227 | ... owner=translations_admin) | ||
379 | 228 | >>> removeSecurityProxy(distro).translationgroup = translationgroup | ||
380 | 213 | >>> logout() | 229 | >>> logout() |
381 | 214 | 230 | ||
383 | 215 | >>> admin_browser.open(package_page) | 231 | >>> translations_browser = setupBrowser("Basic ta@example.com:test") |
384 | 232 | >>> translations_browser.open(package_page) | ||
385 | 216 | 233 | ||
386 | 217 | Of course in this case, the notice about there being no custom language | 234 | Of course in this case, the notice about there being no custom language |
387 | 218 | codes talks about a package, not a project. | 235 | codes talks about a package, not a project. |
388 | 219 | 236 | ||
390 | 220 | >>> tag = find_custom_language_codes_link(admin_browser) | 237 | >>> tag = find_custom_language_codes_link(translations_browser) |
391 | 221 | >>> print extract_text(tag.renderContents()) | 238 | >>> print extract_text(tag.renderContents()) |
392 | 222 | If necessary, you may | 239 | If necessary, you may |
393 | 223 | define custom language codes | 240 | define custom language codes |
394 | 224 | for this package. | 241 | for this package. |
395 | 225 | 242 | ||
398 | 226 | >>> admin_browser.getLink("define custom language codes").click() | 243 | >>> translations_browser.getLink("define custom language codes").click() |
399 | 227 | >>> custom_language_codes_page = admin_browser.url | 244 | >>> custom_language_codes_page = translations_browser.url |
400 | 228 | 245 | ||
402 | 229 | >>> tag = find_tag_by_id(admin_browser.contents, 'empty') | 246 | >>> tag = find_tag_by_id(translations_browser.contents, 'empty') |
403 | 230 | >>> print extract_text(tag.renderContents()) | 247 | >>> print extract_text(tag.renderContents()) |
404 | 231 | No custom language codes have been defined. | 248 | No custom language codes have been defined. |
405 | 232 | 249 | ||
414 | 233 | Again, an admin can add a language code. | 250 | A translations admin can add a language code. |
415 | 234 | 251 | ||
416 | 235 | >>> admin_browser.getLink("Add a custom language code").click() | 252 | >>> translations_browser.getLink("Add a custom language code").click() |
417 | 236 | >>> add_page = admin_browser.url | 253 | >>> add_page = translations_browser.url |
418 | 237 | 254 | ||
419 | 238 | >>> admin_browser.getControl("Language code:").value = 'pt-br' | 255 | >>> translations_browser.getControl("Language code:").value = 'pt-br' |
420 | 239 | >>> admin_browser.getControl("Language:").value = ['pt_BR'] | 256 | >>> translations_browser.getControl("Language:").value = ['pt_BR'] |
421 | 240 | >>> admin_browser.getControl("Add").click() | 257 | >>> translations_browser.getControl("Add").click() |
422 | 241 | 258 | ||
423 | 242 | The language code is displayed. | 259 | The language code is displayed. |
424 | 243 | 260 | ||
426 | 244 | >>> tag = find_tag_by_id(admin_browser.contents, 'nonempty') | 261 | >>> tag = find_tag_by_id(translations_browser.contents, 'nonempty') |
427 | 245 | >>> print extract_text(tag.renderContents()) | 262 | >>> print extract_text(tag.renderContents()) |
428 | 246 | bar in distro uses the following custom language codes: | 263 | bar in distro uses the following custom language codes: |
429 | 247 | Code... ...maps to language | 264 | Code... ...maps to language |
430 | @@ -250,15 +267,15 @@ | |||
431 | 250 | It's also displayed identically on the same package but in another | 267 | It's also displayed identically on the same package but in another |
432 | 251 | release series of the same distribution. | 268 | release series of the same distribution. |
433 | 252 | 269 | ||
436 | 253 | >>> admin_browser.open(page_in_other_series) | 270 | >>> translations_browser.open(page_in_other_series) |
437 | 254 | >>> tag = find_custom_language_codes_link(admin_browser) | 271 | >>> tag = find_custom_language_codes_link(translations_browser) |
438 | 255 | >>> print extract_text(tag.renderContents()) | 272 | >>> print extract_text(tag.renderContents()) |
439 | 256 | If necessary, you may | 273 | If necessary, you may |
440 | 257 | define custom language codes | 274 | define custom language codes |
441 | 258 | for this package. | 275 | for this package. |
442 | 259 | 276 | ||
445 | 260 | >>> admin_browser.getLink("define custom language codes").click() | 277 | >>> translations_browser.getLink("define custom language codes").click() |
446 | 261 | >>> tag = find_tag_by_id(admin_browser.contents, 'nonempty') | 278 | >>> tag = find_tag_by_id(translations_browser.contents, 'nonempty') |
447 | 262 | >>> print extract_text(tag.renderContents()) | 279 | >>> print extract_text(tag.renderContents()) |
448 | 263 | bar in distro uses the following custom language codes: | 280 | bar in distro uses the following custom language codes: |
449 | 264 | Code... ...maps to language | 281 | Code... ...maps to language |
450 | @@ -267,13 +284,13 @@ | |||
451 | 267 | 284 | ||
452 | 268 | The new code has a link there... | 285 | The new code has a link there... |
453 | 269 | 286 | ||
455 | 270 | >>> admin_browser.getLink("pt-br").click() | 287 | >>> translations_browser.getLink("pt-br").click() |
456 | 271 | 288 | ||
457 | 272 | ...and can be deleted. | 289 | ...and can be deleted. |
458 | 273 | 290 | ||
461 | 274 | >>> admin_browser.getLink("remove custom language code").click() | 291 | >>> translations_browser.getLink("remove custom language code").click() |
462 | 275 | >>> admin_browser.getControl("Remove").click() | 292 | >>> translations_browser.getControl("Remove").click() |
463 | 276 | 293 | ||
465 | 277 | >>> tag = find_tag_by_id(admin_browser.contents, 'empty') | 294 | >>> tag = find_tag_by_id(translations_browser.contents, 'empty') |
466 | 278 | >>> print extract_text(tag.renderContents()) | 295 | >>> print extract_text(tag.renderContents()) |
467 | 279 | No custom language codes have been defined. | 296 | No custom language codes have been defined. |
468 | 280 | 297 | ||
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 | 26 | 26 | ||
474 | 27 | <div class="portlet"> | 27 | <div class="portlet"> |
475 | 28 | <ul class="horizontal"> | 28 | <ul class="horizontal"> |
477 | 29 | <li tal:condition="context/required:launchpad.Admin"> | 29 | <li tal:condition="context/required:launchpad.TranslationsAdmin"> |
478 | 30 | <a class="remove sprite" | 30 | <a class="remove sprite" |
479 | 31 | tal:attributes="href context/fmt:url:translations/+remove"> | 31 | tal:attributes="href context/fmt:url:translations/+remove"> |
480 | 32 | remove custom language code | 32 | remove custom language code |
481 | 33 | 33 | ||
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 | 1 | <html | ||
487 | 2 | xmlns="http://www.w3.org/1999/xhtml" | ||
488 | 3 | xmlns:tal="http://xml.zope.org/namespaces/tal" | ||
489 | 4 | xmlns:metal="http://xml.zope.org/namespaces/metal" | ||
490 | 5 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" | ||
491 | 6 | metal:use-macro="view/macro:page/main_only" | ||
492 | 7 | i18n:domain="launchpad"> | ||
493 | 8 | <body> | ||
494 | 9 | <div metal:fill-slot="main"> | ||
495 | 10 | <div metal:use-macro="context/@@launchpad_form/form"> | ||
496 | 11 | <div metal:fill-slot="extra_info" class="documentDescription"> | ||
497 | 12 | You are going to remove the custom language code | ||
498 | 13 | '<tal:language-code replace="view/code" />'. | ||
499 | 14 | </div> | ||
500 | 15 | </div> | ||
501 | 16 | </div> | ||
502 | 17 | </body> | ||
503 | 18 | </html> | ||
504 | 0 | 19 | ||
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 | 33 | <tr> | 33 | <tr> |
510 | 34 | <th>Code...</th> | 34 | <th>Code...</th> |
511 | 35 | <th>...maps to language</th> | 35 | <th>...maps to language</th> |
513 | 36 | <th tal:condition="context/required:launchpad.Admin"></th> | 36 | <th tal:condition="context/required:launchpad.TranslationsAdmin"> |
514 | 37 | </th> | ||
515 | 37 | </tr> | 38 | </tr> |
516 | 38 | </thead> | 39 | </thead> |
517 | 39 | <tbody> | 40 | <tbody> |
518 | @@ -51,7 +52,7 @@ | |||
519 | 51 | — | 52 | — |
520 | 52 | </tal:nolanguage> | 53 | </tal:nolanguage> |
521 | 53 | </td> | 54 | </td> |
523 | 54 | <td tal:condition="context/required:launchpad.Admin"> | 55 | <td tal:condition="context/required:launchpad.TranslationsAdmin"> |
524 | 55 | <a tal:attributes="href entry/fmt:url:translations/+remove" | 56 | <a tal:attributes="href entry/fmt:url:translations/+remove" |
525 | 56 | alt="Remove" | 57 | alt="Remove" |
526 | 57 | title="Remove" | 58 | title="Remove" |
527 | @@ -70,7 +71,7 @@ | |||
528 | 70 | 71 | ||
529 | 71 | <div> | 72 | <div> |
530 | 72 | <a tal:attributes="href context/fmt:url:translations/+add-custom-language-code" | 73 | <a tal:attributes="href context/fmt:url:translations/+add-custom-language-code" |
532 | 73 | tal:condition="context/required:launchpad.Admin" | 74 | tal:condition="context/required:launchpad.TranslationsAdmin" |
533 | 74 | class="add sprite"> | 75 | class="add sprite"> |
534 | 75 | Add a custom language code | 76 | Add a custom language code |
535 | 76 | </a> | 77 | </a> |
536 | 77 | 78 | ||
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 | 65 | </div> | 65 | </div> |
542 | 66 | 66 | ||
543 | 67 | <div class="portlet" | 67 | <div class="portlet" |
545 | 68 | tal:condition="context/required:launchpad.Admin" | 68 | tal:condition="context/required:launchpad.TranslationsAdmin" |
546 | 69 | id="custom-language-codes"> | 69 | id="custom-language-codes"> |
547 | 70 | If necessary, you may | 70 | If necessary, you may |
548 | 71 | <a tal:attributes="href context/fmt:url:translations/+custom-language-codes" | 71 | <a tal:attributes="href context/fmt:url:translations/+custom-language-codes" |
549 | 72 | 72 | ||
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 | 39 | <a tal:attributes="href context/menu:navigation/download/url"> | 39 | <a tal:attributes="href context/menu:navigation/download/url"> |
555 | 40 | download a full tarball</a> with translations. | 40 | download a full tarball</a> with translations. |
556 | 41 | </p> | 41 | </p> |
558 | 42 | <p tal:condition="context/required:launchpad.Admin" | 42 | <p tal:condition="context/distribution_sourcepackage/required:launchpad.TranslationsAdmin" |
559 | 43 | id="custom-language-codes"> | 43 | id="custom-language-codes"> |
560 | 44 | If necessary, you may | 44 | If necessary, you may |
561 | 45 | <a tal:attributes="href context/distribution_sourcepackage/fmt:url:translations/+custom-language-codes" | 45 | <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.