Merge lp:~henninge/launchpad/devel-487137-custom-language-codes into lp:launchpad

Proposed by Henning Eggers
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
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][ui=sinzui][bug=487137] Let project owners and rosetta admins manage custom language codes. Mainly done by Adi.

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://code.launchpad.net/~adiroiban/launchpad/bug-487137/+merge/23901

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 AdminCustomLanguageCodes from security.py. This was the main
bug (and a good reason for not landing Adi's branch) as it shadowed
AdminProductTranslations and thus took this privilege away from
product owners. AdminProductTranslations is exactly what we need now
to allow access to owners & Rosetta admins.

Add AdminDistributionSourcePackageTranslations to grant
launchpad.TranslationsAdmin privileges on a DistributionSourcePackage
to the same persons that have it on the distribution.

Fix AdminCustomLanguageCode to allow access for owners and
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 ==

AdminProductTranslations and AdminDistributionTranslations already do
the right thing and are used by the new adapters.
A custom language code can either be linked to a Product or a

DistributionSourcepackage (combination of distribution and
sourcepacakgename), so that the security adapter has to select the
right adapter to forward to.

AdminCustomLanguageCode already implemented forwarding via the
"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 DistributionSourcePackage a translation group needed
to be added because its owner will have translation admin privileges
within the distribution. A new translations_browser was added for
this person.

== Tests ==

bin/test -vvcm lp.translations -t custom-language-codes.txt

== Demo and Q/A ==

On launchpad.dev login as <email address hidden> (name12) and go to this
page: https://translations.launchpad.dev/evolution
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/translations/templates/sourcepackage-translations.pt
  lib/lp/translations/templates/customlanguagecode-remove.pt
  lib/lp/translations/browser/configure.zcml
  lib/lp/translations/browser/customlanguagecode.py
  lib/canonical/launchpad/security.py
  lib/lp/translations/templates/customlanguagecodes-index.pt
  lib/lp/translations/templates/customlanguagecode-index.pt
  lib/lp/translations/stories/standalone/custom-language-codes.txt
  lib/lp/translations/templates/product-portlet-translatables.pt

To post a comment you must log in.
Revision history for this message
Henning Eggers (henninge) wrote :

Here is the incremental diff in relation to Adi's branch. This MP is only
about these changes.

=== modified file 'lib/canonical/launchpad/security.py'
--- lib/canonical/launchpad/security.py 2010-12-21 15:13:17 +0000
+++ lib/canonical/launchpad/security.py 2010-12-22 12:03:59 +0000
@@ -185,7 +185,6 @@
185from lp.soyuz.interfaces.sourcepackagerelease import ISourcePackageRelease185from lp.soyuz.interfaces.sourcepackagerelease import ISourcePackageRelease
186from lp.translations.interfaces.customlanguagecode import (186from lp.translations.interfaces.customlanguagecode import (
187 ICustomLanguageCode,187 ICustomLanguageCode,
188 IHasCustomLanguageCodes,
189 )188 )
190from lp.translations.interfaces.languagepack import ILanguagePack189from lp.translations.interfaces.languagepack import ILanguagePack
191from lp.translations.interfaces.pofile import IPOFile190from lp.translations.interfaces.pofile import IPOFile
@@ -744,6 +743,7 @@
744# with launchpad.View so that this adapter is used. For now, though, it's743# with launchpad.View so that this adapter is used. For now, though, it's
745# going to be used only on the webservice (which explicitly checks for744# going to be used only on the webservice (which explicitly checks for
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.
746
747class ViewTeamMembership(AuthorizationBase):747class ViewTeamMembership(AuthorizationBase):
748 permission = 'launchpad.View'748 permission = 'launchpad.View'
749 usedfor = ITeamMembership749 usedfor = ITeamMembership
@@ -1728,27 +1728,23 @@
1728 usedfor = ILanguage1728 usedfor = ILanguage
17291729
17301730
1731class AdminCustomLanguageCodes(OnlyRosettaExpertsAndAdmins):1731class AdminCustomLanguageCode(AuthorizationBase):
1732 """Controls administration of custom language codes.
1733
1734 Rosetta experts and Launchpad administrators can administer custom
1735 language codes.
1736 """
1737
1738 permission = 'launchpad.TranslationsAdmin'
1739 usedfor = IHasCustomLanguageCodes
1740
1741
1742class AdminCustomLanguageCode(OnlyRosettaExpertsAndAdmins):
1743 """Controls administration for a custom language code.1732 """Controls administration for a custom language code.
17441733
1745 Rosetta experts and Launchpad administrators can administer a custom1734 Whoever can admin a product's or distribution's translations can also
1746 language code.1735 admin the custom language codes for it.
1747 """1736 """
1748
1749 permission = 'launchpad.TranslationsAdmin'1737 permission = 'launchpad.TranslationsAdmin'
1750 usedfor = ICustomLanguageCode1738 usedfor = ICustomLanguageCode
17511739
1740 def checkAuthenticated(self, user):
1741 if self.obj.product is not None:
1742 return AdminProductTranslations(
1743 self.obj.product).checkAuthenticated(user)
1744 else:
1745 return AdminDistributionTranslations(
1746 self.obj.distribution).checkAuthenticated(user)
1747
17521748
1753class AccessBranch(AuthorizationBase):1749class AccessBranch(AuthorizationBase):
1754 """Controls visibility of branches.1750 """Controls visibility of branches.
@@ -1839,6 +1835,12 @@
1839 self.obj.distribution).checkAuthenticated(user))1835 self.obj.distribution).checkAuthenticated(user))
18401836
18411837
1838class AdminDistributionSourcePackageTranslations(
1839 AdminDistroSeriesTranslations):
1840 """DistributionSourcePackage objects link to a distribution, too."""
1841 usedfor = IDistributionSourcePackage
1842
1843
1842class AdminProductSeriesTranslations(AuthorizationBase):1844class AdminProductSeriesTranslations(AuthorizationBase):
1843 permission = 'launchpad.TranslationsAdmin'1845 permission = 'launchpad.TranslationsAdmin'
1844 usedfor = IProductSeries1846 usedfor = IProductSeries
18451847
=== modified file 'lib/lp/translations/browser/configure.zcml'
--- lib/lp/translations/browser/configure.zcml 2010-12-21 15:13:17 +0000
+++ lib/lp/translations/browser/configure.zcml 2010-12-22 12:01:59 +0000
@@ -941,7 +941,7 @@
941 for="lp.registry.interfaces.distribution.IDistribution"941 for="lp.registry.interfaces.distribution.IDistribution"
942 permission="zope.Public"942 permission="zope.Public"
943 template="../templates/translations-portlet-not-using-launchpad-extra.pt"943 template="../templates/translations-portlet-not-using-launchpad-extra.pt"
944 layer="lp.translations.publisher.TranslationsLayer"/> 944 layer="lp.translations.publisher.TranslationsLayer"/>
945 <browser:page945 <browser:page
946 name="+portlet-configuration"946 name="+portlet-configuration"
947 for="lp.registry.interfaces.distribution.IDistribution"947 for="lp.registry.interfaces.distribution.IDistribution"
948948
=== modified file 'lib/lp/translations/browser/customlanguagecode.py'
--- lib/lp/translations/browser/customlanguagecode.py 2010-12-21 15:13:17 +0000
+++ lib/lp/translations/browser/customlanguagecode.py 2010-12-22 12:03:05 +0000
@@ -46,6 +46,7 @@
4646
47class CustomLanguageCodeBreadcrumb(Breadcrumb):47class CustomLanguageCodeBreadcrumb(Breadcrumb):
48 """Breadcrumb for a `CustomLanguageCode`."""48 """Breadcrumb for a `CustomLanguageCode`."""
49
49 @property50 @property
50 def text(self):51 def text(self):
51 return smartquote(52 return smartquote(
@@ -164,6 +165,7 @@
164class HasCustomLanguageCodesTraversalMixin:165class HasCustomLanguageCodesTraversalMixin:
165 """Navigate from an `IHasCustomLanguageCodes` to a `CustomLanguageCode`.166 """Navigate from an `IHasCustomLanguageCodes` to a `CustomLanguageCode`.
166 """167 """
168
167 @stepthrough('+customcode')169 @stepthrough('+customcode')
168 def traverseCustomCode(self, name):170 def traverseCustomCode(self, name):
169 """Traverse +customcode URLs."""171 """Traverse +customcode URLs."""
170172
=== modified file 'lib/lp/translations/stories/standalone/custom-language-codes.txt'
--- lib/lp/translations/stories/standalone/custom-language-codes.txt 2010-12-21 15:13:17 +0000
+++ lib/lp/translations/stories/standalone/custom-language-codes.txt 2010-12-22 12:04:22 +0000
@@ -38,16 +38,18 @@
38 >>> owner_browser = setupBrowser("Basic o@example.com:test")38 >>> owner_browser = setupBrowser("Basic o@example.com:test")
39 >>> rosetta_admin_browser = setupRosettaExpertBrowser()39 >>> rosetta_admin_browser = setupRosettaExpertBrowser()
4040
41A Launchpad administrator or Rosetta expert sees the link to the custom41The project's owner sees the link to the custom language codes on a project's
42language codes on a project's main translations page.42main translations page.
4343
44 >>> admin_browser.open(product_page)44 >>> owner_browser.open(product_page)
45 >>> tag = find_custom_language_codes_link(admin_browser)45 >>> tag = find_custom_language_codes_link(owner_browser)
46 >>> print extract_text(tag.renderContents())46 >>> print extract_text(tag.renderContents())
47 If necessary, you may47 If necessary, you may
48 define custom language codes48 define custom language codes
49 for this project.49 for this project.
5050
51Translation admins also have access to this link.
52
51 >>> rosetta_admin_browser.open(product_page)53 >>> rosetta_admin_browser.open(product_page)
52 >>> tag = find_custom_language_codes_link(rosetta_admin_browser)54 >>> tag = find_custom_language_codes_link(rosetta_admin_browser)
53 >>> print extract_text(tag.renderContents())55 >>> print extract_text(tag.renderContents())
@@ -57,39 +59,37 @@
5759
58The link goes to the custom language codes management page.60The link goes to the custom language codes management page.
5961
60 >>> rosetta_admin_browser.getLink("define custom language codes").click()62 >>> owner_browser.getLink("define custom language codes").click()
61 >>> custom_language_codes_page = rosetta_admin_browser.url63 >>> custom_language_codes_page = owner_browser.url
6264
63Non-admins, even the project's owner, don't see this link. We do not65Other users don't see this link.
64advertise this feature, since the proper solution is generally to use66
65the right language codes.67 >>> user_browser.open(product_page)
6668 >>> print find_custom_language_codes_link(user_browser)
67 >>> owner_browser.open(product_page)
68 >>> print find_custom_language_codes_link(owner_browser)
69 None69 None
7070
71Initially the page shows no custom language codes for the project.71Initially the page shows no custom language codes for the project.
7272
73 >>> tag = find_tag_by_id(rosetta_admin_browser.contents, 'empty')73 >>> tag = find_tag_by_id(owner_browser.contents, 'empty')
74 >>> print extract_text(tag.renderContents())74 >>> print extract_text(tag.renderContents())
75 No custom language codes have been defined.75 No custom language codes have been defined.
7676
77The admin can add a custom language code.77There is a link to add a custom language code.
7878
79 >>> rosetta_admin_browser.getLink("Add a custom language code").click()79 >>> owner_browser.getLink("Add a custom language code").click()
80 >>> add_page = rosetta_admin_browser.url80 >>> add_page = owner_browser.url
8181
82 >>> rosetta_admin_browser.getControl("Language code:").value = 'no'82 >>> owner_browser.getControl("Language code:").value = 'no'
83 >>> rosetta_admin_browser.getControl("Language:").value = ['nn']83 >>> owner_browser.getControl("Language:").value = ['nn']
84 >>> rosetta_admin_browser.getControl("Add").click()84 >>> owner_browser.getControl("Add").click()
8585
86This leads back to the custom language codes overview, where the new86This leads back to the custom language codes overview, where the new
87code is now shown.87code is now shown.
8888
89 >>> rosetta_admin_browser.url == custom_language_codes_page89 >>> owner_browser.url == custom_language_codes_page
90 True90 True
9191
92 >>> tag = find_tag_by_id(rosetta_admin_browser.contents, 'nonempty')92 >>> tag = find_tag_by_id(owner_browser.contents, 'nonempty')
93 >>> print extract_text(tag.renderContents())93 >>> print extract_text(tag.renderContents())
94 Foo uses the following custom language codes:94 Foo uses the following custom language codes:
95 Code... ...maps to language95 Code... ...maps to language
@@ -98,8 +98,8 @@
98There is an overview page for the custom code, though there's not much98There is an overview page for the custom code, though there's not much
99to see there.99to see there.
100100
101 >>> rosetta_admin_browser.getLink("no").click()101 >>> owner_browser.getLink("no").click()
102 >>> main = find_main_content(rosetta_admin_browser.contents)102 >>> main = find_main_content(owner_browser.contents)
103 >>> print extract_text(main.renderContents())103 >>> print extract_text(main.renderContents())
104 Foo Translations Custom language code ...no...104 Foo Translations Custom language code ...no...
105 For Foo, uploads with the language code105 For Foo, uploads with the language code
@@ -111,79 +111,79 @@
111111
112The overview page leads back to the custom language codes overview.112The overview page leads back to the custom language codes overview.
113113
114 >>> code_page = rosetta_admin_browser.url114 >>> code_page = owner_browser.url
115 >>> rosetta_admin_browser.getLink(115 >>> owner_browser.getLink(
116 ... "custom language codes overview").click()116 ... "custom language codes overview").click()
117 >>> rosetta_admin_browser.url == custom_language_codes_page117 >>> owner_browser.url == custom_language_codes_page
118 True118 True
119119
120 >>> rosetta_admin_browser.open(code_page)120 >>> owner_browser.open(code_page)
121121
122There is also a link for removing codes. The admin follows the link and122There is also a link for removing codes. The owner follows the link and
123removes the "no" custom language code.123removes the "no" custom language code.
124124
125 >>> rosetta_admin_browser.getLink("remove custom language code").click()125 >>> owner_browser.getLink("remove custom language code").click()
126 >>> remove_page = rosetta_admin_browser.url126 >>> remove_page = owner_browser.url
127 >>> rosetta_admin_browser.getControl("Remove").click()127 >>> owner_browser.getControl("Remove").click()
128128
129This leads back to the overview page.129This leads back to the overview page.
130130
131 >>> rosetta_admin_browser.url == custom_language_codes_page131 >>> owner_browser.url == custom_language_codes_page
132 True132 True
133133
134 >>> tag = find_tag_by_id(rosetta_admin_browser.contents, 'empty')
135 >>> print extract_text(tag.renderContents())
136 No custom language codes have been defined.
137
138
139Non-admin access
140================
141
142A non-admin can see the page, actually, if they know the URL. This can
143be convenient for debugging.
144
145 >>> owner_browser.open(custom_language_codes_page)
146
147 >>> tag = find_tag_by_id(owner_browser.contents, 'empty')134 >>> tag = find_tag_by_id(owner_browser.contents, 'empty')
148 >>> print extract_text(tag.renderContents())135 >>> print extract_text(tag.renderContents())
149 No custom language codes have been defined.136 No custom language codes have been defined.
150137
138
139Unprivileged access
140===================
141
142A unprivileged user can see the page, actually, if they know the URL.
143This can be convenient for debugging.
144
145 >>> user_browser.open(custom_language_codes_page)
146
147 >>> tag = find_tag_by_id(user_browser.contents, 'empty')
148 >>> print extract_text(tag.renderContents())
149 No custom language codes have been defined.
150
151However all they get is a read-only version of the page.151However all they get is a read-only version of the page.
152152
153 >>> owner_browser.getLink("Add a custom language code").click()153 >>> user_browser.getLink("Add a custom language code").click()
154 Traceback (most recent call last):154 Traceback (most recent call last):
155 ...155 ...
156 LinkNotFoundError156 LinkNotFoundError
157157
158The page for adding custom language codes is not accessible to them.158The page for adding custom language codes is not accessible to them.
159159
160 >>> user_browser.open(add_page)
161 Traceback (most recent call last):
162 ...
163 Unauthorized: ...
164
165And naturally, if the owner creates a custom language code again, an
166unprivileged user can't remove it.
167
160 >>> owner_browser.open(add_page)168 >>> owner_browser.open(add_page)
161 Traceback (most recent call last):169 >>> owner_browser.getControl("Language code:").value = 'no'
162 ...170 >>> owner_browser.getControl("Language:").value = ['nn']
163 Unauthorized: ...171 >>> owner_browser.getControl("Add").click()
164172
165And naturally, if an admin creates a custom language code again, a173 >>> user_browser.open(custom_language_codes_page)
166non-admin can't remove it.174 >>> tag = find_tag_by_id(user_browser.contents, 'nonempty')
167
168 >>> rosetta_admin_browser.open(add_page)
169 >>> rosetta_admin_browser.getControl("Language code:").value = 'no'
170 >>> rosetta_admin_browser.getControl("Language:").value = ['nn']
171 >>> rosetta_admin_browser.getControl("Add").click()
172
173 >>> owner_browser.open(custom_language_codes_page)
174 >>> tag = find_tag_by_id(owner_browser.contents, 'nonempty')
175 >>> print extract_text(tag.renderContents())175 >>> print extract_text(tag.renderContents())
176 Foo uses the following custom language codes:176 Foo uses the following custom language codes:
177 Code... ...maps to language177 Code... ...maps to language
178 no Norwegian Nynorsk178 no Norwegian Nynorsk
179179
180 >>> owner_browser.getLink("no").click()180 >>> user_browser.getLink("no").click()
181 >>> owner_browser.getLink("remove custom language code")181 >>> user_browser.getLink("remove custom language code")
182 Traceback (most recent call last):182 Traceback (most recent call last):
183 ...183 ...
184 LinkNotFoundError184 LinkNotFoundError
185185
186 >>> owner_browser.open(remove_page)186 >>> user_browser.open(remove_page)
187 Traceback (most recent call last):187 Traceback (most recent call last):
188 ...188 ...
189 Unauthorized: ...189 Unauthorized: ...
@@ -197,7 +197,8 @@
197package--i.e. the combination of a distribution and a source package197package--i.e. the combination of a distribution and a source package
198name. However, since there is no Translations page for that type of198name. However, since there is no Translations page for that type of
199object (and we'd probably never go there if there were), the link is199object (and we'd probably never go there if there were), the link is
200shown on the source package page.200shown on the source package page. For distributions, the owner of the
201distribution's translation group is a translations administrator.
201202
202 >>> login(ANONYMOUS)203 >>> login(ANONYMOUS)
203 >>> from lp.registry.model.sourcepackage import SourcePackage204 >>> from lp.registry.model.sourcepackage import SourcePackage
@@ -219,38 +220,44 @@
219 ... distroseries=other_series,220 ... distroseries=other_series,
220 ... sourcepackagename=package.sourcepackagename),221 ... sourcepackagename=package.sourcepackagename),
221 ... rootsite="translations")222 ... rootsite="translations")
223 >>> translations_admin = factory.makePerson(
224 ... email='ta@example.com', password='test')
225 >>> translationgroup = factory.makeTranslationGroup(
226 ... owner=translations_admin)
227 >>> removeSecurityProxy(distro).translationgroup = translationgroup
222 >>> logout()228 >>> logout()
223229
224 >>> rosetta_admin_browser.open(package_page)230 >>> translations_browser = setupBrowser("Basic ta@example.com:test")
231 >>> translations_browser.open(package_page)
225232
226Of course in this case, the notice about there being no custom language233Of course in this case, the notice about there being no custom language
227codes talks about a package, not a project.234codes talks about a package, not a project.
228235
229 >>> tag = find_custom_language_codes_link(rosetta_admin_browser)236 >>> tag = find_custom_language_codes_link(translations_browser)
230 >>> print extract_text(tag.renderContents())237 >>> print extract_text(tag.renderContents())
231 If necessary, you may238 If necessary, you may
232 define custom language codes239 define custom language codes
233 for this package.240 for this package.
234241
235 >>> rosetta_admin_browser.getLink("define custom language codes").click()242 >>> translations_browser.getLink("define custom language codes").click()
236 >>> custom_language_codes_page = rosetta_admin_browser.url243 >>> custom_language_codes_page = translations_browser.url
237244
238 >>> tag = find_tag_by_id(rosetta_admin_browser.contents, 'empty')245 >>> tag = find_tag_by_id(translations_browser.contents, 'empty')
239 >>> print extract_text(tag.renderContents())246 >>> print extract_text(tag.renderContents())
240 No custom language codes have been defined.247 No custom language codes have been defined.
241248
242Again, an admin can add a language code.249A translations admin can add a language code.
243250
244 >>> rosetta_admin_browser.getLink("Add a custom language code").click()251 >>> translations_browser.getLink("Add a custom language code").click()
245 >>> add_page = rosetta_admin_browser.url252 >>> add_page = translations_browser.url
246253
247 >>> rosetta_admin_browser.getControl("Language code:").value = 'pt-br'254 >>> translations_browser.getControl("Language code:").value = 'pt-br'
248 >>> rosetta_admin_browser.getControl("Language:").value = ['pt_BR']255 >>> translations_browser.getControl("Language:").value = ['pt_BR']
249 >>> rosetta_admin_browser.getControl("Add").click()256 >>> translations_browser.getControl("Add").click()
250257
251The language code is displayed.258The language code is displayed.
252259
253 >>> tag = find_tag_by_id(rosetta_admin_browser.contents, 'nonempty')260 >>> tag = find_tag_by_id(translations_browser.contents, 'nonempty')
254 >>> print extract_text(tag.renderContents())261 >>> print extract_text(tag.renderContents())
255 bar in distro uses the following custom language codes:262 bar in distro uses the following custom language codes:
256 Code... ...maps to language263 Code... ...maps to language
@@ -259,15 +266,15 @@
259It's also displayed identically on the same package but in another266It's also displayed identically on the same package but in another
260release series of the same distribution.267release series of the same distribution.
261268
262 >>> rosetta_admin_browser.open(page_in_other_series)269 >>> translations_browser.open(page_in_other_series)
263 >>> tag = find_custom_language_codes_link(rosetta_admin_browser)270 >>> tag = find_custom_language_codes_link(translations_browser)
264 >>> print extract_text(tag.renderContents())271 >>> print extract_text(tag.renderContents())
265 If necessary, you may272 If necessary, you may
266 define custom language codes273 define custom language codes
267 for this package.274 for this package.
268275
269 >>> rosetta_admin_browser.getLink("define custom language codes").click()276 >>> translations_browser.getLink("define custom language codes").click()
270 >>> tag = find_tag_by_id(rosetta_admin_browser.contents, 'nonempty')277 >>> tag = find_tag_by_id(translations_browser.contents, 'nonempty')
271 >>> print extract_text(tag.renderContents())278 >>> print extract_text(tag.renderContents())
272 bar in distro uses the following custom language codes:279 bar in distro uses the following custom language codes:
273 Code... ...maps to language280 Code... ...maps to language
@@ -276,13 +283,13 @@
276283
277The new code has a link there...284The new code has a link there...
278285
279 >>> rosetta_admin_browser.getLink("pt-br").click()286 >>> translations_browser.getLink("pt-br").click()
280287
281...and can be deleted.288...and can be deleted.
282289
283 >>> rosetta_admin_browser.getLink("remove custom language code").click()290 >>> translations_browser.getLink("remove custom language code").click()
284 >>> rosetta_admin_browser.getControl("Remove").click()291 >>> translations_browser.getControl("Remove").click()
285292
286 >>> tag = find_tag_by_id(rosetta_admin_browser.contents, 'empty')293 >>> tag = find_tag_by_id(translations_browser.contents, 'empty')
287 >>> print extract_text(tag.renderContents())294 >>> print extract_text(tag.renderContents())
288 No custom language codes have been defined.295 No custom language codes have been defined.
Revision history for this message
Jelmer Vernooij (jelmer) :
review: Approve (code)
Revision history for this message
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://people.canonical.com/~henninge/screenshots/custom_language_codes_1_product.png

Clicking that link will bring you to the listing of custom language codes for this product which is still empty.
http://people.canonical.com/~henninge/screenshots/custom_language_codes_2_index_empty.png

Clicking "Add a custom language code" will bring you to the add form.
http://people.canonical.com/~henninge/screenshots/custom_language_codes_3_add.png

Now the listing has one entry.
http://people.canonical.com/~henninge/screenshots/custom_language_codes_4_index_one.png

Clicking on the custom code brings up the details page for this code.
http://people.canonical.com/~henninge/screenshots/custom_language_codes_5_one.png

And trying to remove it will bring up the removal page.
http://people.canonical.com/~henninge/screenshots/custom_language_codes_6_remove.png

Almost could have done a screencast ... ;)

Revision history for this message
Curtis Hovey (sinzui) wrote :

Hi Henning.

This looks good. I have a question. Why is custom_language_codes_5_one.png missing a header? Maybe Custom language code 'qq' for Evolution?

review: Needs Information (ui)
Revision history for this message
Henning Eggers (henninge) wrote :

Good catch, thank you! Here is a screenshot of the fixed view.

http://people.canonical.com/~henninge/screenshots/custom_language_codes_5a_one.png

Revision history for this message
Curtis Hovey (sinzui) wrote :

I think this is good to land.

review: Approve (ui)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/security.py'
--- lib/canonical/launchpad/security.py 2010-12-17 20:28:40 +0000
+++ lib/canonical/launchpad/security.py 2010-12-23 09:44:44 +0000
@@ -183,6 +183,9 @@
183 IPackageUploadQueue,183 IPackageUploadQueue,
184 )184 )
185from lp.soyuz.interfaces.sourcepackagerelease import ISourcePackageRelease185from lp.soyuz.interfaces.sourcepackagerelease import ISourcePackageRelease
186from lp.translations.interfaces.customlanguagecode import (
187 ICustomLanguageCode,
188 )
186from lp.translations.interfaces.languagepack import ILanguagePack189from lp.translations.interfaces.languagepack import ILanguagePack
187from lp.translations.interfaces.pofile import IPOFile190from lp.translations.interfaces.pofile import IPOFile
188from lp.translations.interfaces.potemplate import IPOTemplate191from lp.translations.interfaces.potemplate import IPOTemplate
@@ -740,6 +743,7 @@
740# with launchpad.View so that this adapter is used. For now, though, it's743# with launchpad.View so that this adapter is used. For now, though, it's
741# going to be used only on the webservice (which explicitly checks for744# going to be used only on the webservice (which explicitly checks for
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.
746
743class ViewTeamMembership(AuthorizationBase):747class ViewTeamMembership(AuthorizationBase):
744 permission = 'launchpad.View'748 permission = 'launchpad.View'
745 usedfor = ITeamMembership749 usedfor = ITeamMembership
@@ -1724,6 +1728,24 @@
1724 usedfor = ILanguage1728 usedfor = ILanguage
17251729
17261730
1731class AdminCustomLanguageCode(AuthorizationBase):
1732 """Controls administration for a custom language code.
1733
1734 Whoever can admin a product's or distribution's translations can also
1735 admin the custom language codes for it.
1736 """
1737 permission = 'launchpad.TranslationsAdmin'
1738 usedfor = ICustomLanguageCode
1739
1740 def checkAuthenticated(self, user):
1741 if self.obj.product is not None:
1742 return AdminProductTranslations(
1743 self.obj.product).checkAuthenticated(user)
1744 else:
1745 return AdminDistributionTranslations(
1746 self.obj.distribution).checkAuthenticated(user)
1747
1748
1727class AccessBranch(AuthorizationBase):1749class AccessBranch(AuthorizationBase):
1728 """Controls visibility of branches.1750 """Controls visibility of branches.
17291751
@@ -1813,6 +1835,12 @@
1813 self.obj.distribution).checkAuthenticated(user))1835 self.obj.distribution).checkAuthenticated(user))
18141836
18151837
1838class AdminDistributionSourcePackageTranslations(
1839 AdminDistroSeriesTranslations):
1840 """DistributionSourcePackage objects link to a distribution, too."""
1841 usedfor = IDistributionSourcePackage
1842
1843
1816class AdminProductSeriesTranslations(AuthorizationBase):1844class AdminProductSeriesTranslations(AuthorizationBase):
1817 permission = 'launchpad.TranslationsAdmin'1845 permission = 'launchpad.TranslationsAdmin'
1818 usedfor = IProductSeries1846 usedfor = IProductSeries
18191847
=== modified file 'lib/lp/translations/browser/configure.zcml'
--- lib/lp/translations/browser/configure.zcml 2010-12-01 11:26:57 +0000
+++ lib/lp/translations/browser/configure.zcml 2010-12-23 09:44:44 +0000
@@ -941,7 +941,7 @@
941 for="lp.registry.interfaces.distribution.IDistribution"941 for="lp.registry.interfaces.distribution.IDistribution"
942 permission="zope.Public"942 permission="zope.Public"
943 template="../templates/translations-portlet-not-using-launchpad-extra.pt"943 template="../templates/translations-portlet-not-using-launchpad-extra.pt"
944 layer="lp.translations.publisher.TranslationsLayer"/> 944 layer="lp.translations.publisher.TranslationsLayer"/>
945 <browser:page945 <browser:page
946 name="+portlet-configuration"946 name="+portlet-configuration"
947 for="lp.registry.interfaces.distribution.IDistribution"947 for="lp.registry.interfaces.distribution.IDistribution"
@@ -1042,9 +1042,9 @@
1042 <browser:page1042 <browser:page
1043 name="+remove"1043 name="+remove"
1044 for="lp.translations.interfaces.customlanguagecode.ICustomLanguageCode"1044 for="lp.translations.interfaces.customlanguagecode.ICustomLanguageCode"
1045 permission="launchpad.Admin"1045 permission="launchpad.TranslationsAdmin"
1046 class="lp.translations.browser.customlanguagecode.CustomLanguageCodeRemoveView"1046 class="lp.translations.browser.customlanguagecode.CustomLanguageCodeRemoveView"
1047 template="../../app/templates/generic-edit.pt"1047 template="../templates/customlanguagecode-remove.pt"
1048 layer="lp.translations.publisher.TranslationsLayer"/>1048 layer="lp.translations.publisher.TranslationsLayer"/>
10491049
1050<!-- IHasCustomLanguageCodes -->1050<!-- IHasCustomLanguageCodes -->
@@ -1062,7 +1062,7 @@
1062 layer="lp.translations.publisher.TranslationsLayer"1062 layer="lp.translations.publisher.TranslationsLayer"
1063 class="lp.translations.browser.customlanguagecode.CustomLanguageCodeAddView"1063 class="lp.translations.browser.customlanguagecode.CustomLanguageCodeAddView"
1064 template="../templates/customlanguagecode-add.pt"1064 template="../templates/customlanguagecode-add.pt"
1065 permission="launchpad.Admin"/>1065 permission="launchpad.TranslationsAdmin"/>
10661066
1067 </facet>1067 </facet>
10681068
10691069
=== modified file 'lib/lp/translations/browser/customlanguagecode.py'
--- lib/lp/translations/browser/customlanguagecode.py 2010-12-01 11:26:57 +0000
+++ lib/lp/translations/browser/customlanguagecode.py 2010-12-23 09:44:44 +0000
@@ -11,7 +11,7 @@
11 'CustomLanguageCodeView',11 'CustomLanguageCodeView',
12 'HasCustomLanguageCodesNavigation',12 'HasCustomLanguageCodesNavigation',
13 'HasCustomLanguageCodesTraversalMixin',13 'HasCustomLanguageCodesTraversalMixin',
14 ]14 ]
1515
1616
17import re17import re
@@ -46,6 +46,7 @@
4646
47class CustomLanguageCodeBreadcrumb(Breadcrumb):47class CustomLanguageCodeBreadcrumb(Breadcrumb):
48 """Breadcrumb for a `CustomLanguageCode`."""48 """Breadcrumb for a `CustomLanguageCode`."""
49
49 @property50 @property
50 def text(self):51 def text(self):
51 return smartquote(52 return smartquote(
@@ -126,6 +127,13 @@
126class CustomLanguageCodeView(LaunchpadView):127class CustomLanguageCodeView(LaunchpadView):
127 schema = ICustomLanguageCode128 schema = ICustomLanguageCode
128129
130 @property
131 def label(self):
132 target_displayname = self.context.translation_target.displayname
133 return smartquote(
134 'Custom language code "%s" for %s' % (
135 self.context.language_code, target_displayname))
136
129137
130class CustomLanguageCodeRemoveView(LaunchpadFormView):138class CustomLanguageCodeRemoveView(LaunchpadFormView):
131 """View for removing a `CustomLanguageCode`."""139 """View for removing a `CustomLanguageCode`."""
@@ -164,6 +172,7 @@
164class HasCustomLanguageCodesTraversalMixin:172class HasCustomLanguageCodesTraversalMixin:
165 """Navigate from an `IHasCustomLanguageCodes` to a `CustomLanguageCode`.173 """Navigate from an `IHasCustomLanguageCodes` to a `CustomLanguageCode`.
166 """174 """
175
167 @stepthrough('+customcode')176 @stepthrough('+customcode')
168 def traverseCustomCode(self, name):177 def traverseCustomCode(self, name):
169 """Traverse +customcode URLs."""178 """Traverse +customcode URLs."""
170179
=== modified file 'lib/lp/translations/stories/standalone/custom-language-codes.txt'
--- lib/lp/translations/stories/standalone/custom-language-codes.txt 2010-08-31 23:03:45 +0000
+++ lib/lp/translations/stories/standalone/custom-language-codes.txt 2010-12-23 09:44:44 +0000
@@ -36,12 +36,22 @@
36 >>> logout()36 >>> logout()
3737
38 >>> owner_browser = setupBrowser("Basic o@example.com:test")38 >>> owner_browser = setupBrowser("Basic o@example.com:test")
3939 >>> rosetta_admin_browser = setupRosettaExpertBrowser()
40An administrator sees the link to the custom language codes on a40
41project's main translations page.41The project's owner sees the link to the custom language codes on a project's
4242main translations page.
43 >>> admin_browser.open(product_page)43
44 >>> tag = find_custom_language_codes_link(admin_browser)44 >>> owner_browser.open(product_page)
45 >>> tag = find_custom_language_codes_link(owner_browser)
46 >>> print extract_text(tag.renderContents())
47 If necessary, you may
48 define custom language codes
49 for this project.
50
51Translation admins also have access to this link.
52
53 >>> rosetta_admin_browser.open(product_page)
54 >>> tag = find_custom_language_codes_link(rosetta_admin_browser)
45 >>> print extract_text(tag.renderContents())55 >>> print extract_text(tag.renderContents())
46 If necessary, you may56 If necessary, you may
47 define custom language codes57 define custom language codes
@@ -49,39 +59,37 @@
4959
50The link goes to the custom language codes management page.60The link goes to the custom language codes management page.
5161
52 >>> admin_browser.getLink("define custom language codes").click()62 >>> owner_browser.getLink("define custom language codes").click()
53 >>> custom_language_codes_page = admin_browser.url63 >>> custom_language_codes_page = owner_browser.url
5464
55Non-admins, even the project's owner, don't see this link. We do not65Other users don't see this link.
56advertise this feature, since the proper solution is generally to use66
57the right language codes.67 >>> user_browser.open(product_page)
5868 >>> print find_custom_language_codes_link(user_browser)
59 >>> owner_browser.open(product_page)
60 >>> print find_custom_language_codes_link(owner_browser)
61 None69 None
6270
63Initially the page shows no custom language codes for the project.71Initially the page shows no custom language codes for the project.
6472
65 >>> tag = find_tag_by_id(admin_browser.contents, 'empty')73 >>> tag = find_tag_by_id(owner_browser.contents, 'empty')
66 >>> print extract_text(tag.renderContents())74 >>> print extract_text(tag.renderContents())
67 No custom language codes have been defined.75 No custom language codes have been defined.
6876
69The admin can add a custom language code.77There is a link to add a custom language code.
7078
71 >>> admin_browser.getLink("Add a custom language code").click()79 >>> owner_browser.getLink("Add a custom language code").click()
72 >>> add_page = admin_browser.url80 >>> add_page = owner_browser.url
7381
74 >>> admin_browser.getControl("Language code:").value = 'no'82 >>> owner_browser.getControl("Language code:").value = 'no'
75 >>> admin_browser.getControl("Language:").value = ['nn']83 >>> owner_browser.getControl("Language:").value = ['nn']
76 >>> admin_browser.getControl("Add").click()84 >>> owner_browser.getControl("Add").click()
7785
78This leads back to the custom language codes overview, where the new86This leads back to the custom language codes overview, where the new
79code is now shown.87code is now shown.
8088
81 >>> admin_browser.url == custom_language_codes_page89 >>> owner_browser.url == custom_language_codes_page
82 True90 True
8391
84 >>> tag = find_tag_by_id(admin_browser.contents, 'nonempty')92 >>> tag = find_tag_by_id(owner_browser.contents, 'nonempty')
85 >>> print extract_text(tag.renderContents())93 >>> print extract_text(tag.renderContents())
86 Foo uses the following custom language codes:94 Foo uses the following custom language codes:
87 Code... ...maps to language95 Code... ...maps to language
@@ -90,9 +98,10 @@
90There is an overview page for the custom code, though there's not much98There is an overview page for the custom code, though there's not much
91to see there.99to see there.
92100
93 >>> admin_browser.getLink("no").click()101 >>> owner_browser.getLink("no").click()
94 >>> main = find_main_content(admin_browser.contents)102 >>> main = find_main_content(owner_browser.contents)
95 >>> print extract_text(main.renderContents())103 >>> print extract_text(main.renderContents())
104 Custom language code ...no... for Foo
96 Foo Translations Custom language code ...no...105 Foo Translations Custom language code ...no...
97 For Foo, uploads with the language code106 For Foo, uploads with the language code
98 &ldquo;no&rdquo;107 &ldquo;no&rdquo;
@@ -103,78 +112,79 @@
103112
104The overview page leads back to the custom language codes overview.113The overview page leads back to the custom language codes overview.
105114
106 >>> code_page = admin_browser.url115 >>> code_page = owner_browser.url
107 >>> admin_browser.getLink("custom language codes overview").click()116 >>> owner_browser.getLink(
108 >>> admin_browser.url == custom_language_codes_page117 ... "custom language codes overview").click()
118 >>> owner_browser.url == custom_language_codes_page
109 True119 True
110120
111 >>> admin_browser.open(code_page)121 >>> owner_browser.open(code_page)
112122
113There is also a link for removing codes. The admin follows the link and123There is also a link for removing codes. The owner follows the link and
114removes the "no" custom language code.124removes the "no" custom language code.
115125
116 >>> admin_browser.getLink("remove custom language code").click()126 >>> owner_browser.getLink("remove custom language code").click()
117 >>> remove_page = admin_browser.url127 >>> remove_page = owner_browser.url
118 >>> admin_browser.getControl("Remove").click()128 >>> owner_browser.getControl("Remove").click()
119129
120This leads back to the overview page.130This leads back to the overview page.
121131
122 >>> admin_browser.url == custom_language_codes_page132 >>> owner_browser.url == custom_language_codes_page
123 True133 True
124134
125 >>> tag = find_tag_by_id(admin_browser.contents, 'empty')
126 >>> print extract_text(tag.renderContents())
127 No custom language codes have been defined.
128
129
130Non-admin access
131================
132
133A non-admin can see the page, actually, if they know the URL. This can
134be convenient for debugging.
135
136 >>> owner_browser.open(custom_language_codes_page)
137
138 >>> tag = find_tag_by_id(owner_browser.contents, 'empty')135 >>> tag = find_tag_by_id(owner_browser.contents, 'empty')
139 >>> print extract_text(tag.renderContents())136 >>> print extract_text(tag.renderContents())
140 No custom language codes have been defined.137 No custom language codes have been defined.
141138
139
140Unprivileged access
141===================
142
143A unprivileged user can see the page, actually, if they know the URL.
144This can be convenient for debugging.
145
146 >>> user_browser.open(custom_language_codes_page)
147
148 >>> tag = find_tag_by_id(user_browser.contents, 'empty')
149 >>> print extract_text(tag.renderContents())
150 No custom language codes have been defined.
151
142However all they get is a read-only version of the page.152However all they get is a read-only version of the page.
143153
144 >>> owner_browser.getLink("Add a custom language code").click()154 >>> user_browser.getLink("Add a custom language code").click()
145 Traceback (most recent call last):155 Traceback (most recent call last):
146 ...156 ...
147 LinkNotFoundError157 LinkNotFoundError
148158
149The page for adding custom language codes is not accessible to them.159The page for adding custom language codes is not accessible to them.
150160
161 >>> user_browser.open(add_page)
162 Traceback (most recent call last):
163 ...
164 Unauthorized: ...
165
166And naturally, if the owner creates a custom language code again, an
167unprivileged user can't remove it.
168
151 >>> owner_browser.open(add_page)169 >>> owner_browser.open(add_page)
152 Traceback (most recent call last):170 >>> owner_browser.getControl("Language code:").value = 'no'
153 ...171 >>> owner_browser.getControl("Language:").value = ['nn']
154 Unauthorized: ...172 >>> owner_browser.getControl("Add").click()
155173
156And naturally, if an admin creates a custom language code again, a174 >>> user_browser.open(custom_language_codes_page)
157non-admin can't remove it.175 >>> tag = find_tag_by_id(user_browser.contents, 'nonempty')
158
159 >>> admin_browser.open(add_page)
160 >>> admin_browser.getControl("Language code:").value = 'no'
161 >>> admin_browser.getControl("Language:").value = ['nn']
162 >>> admin_browser.getControl("Add").click()
163
164 >>> owner_browser.open(custom_language_codes_page)
165 >>> tag = find_tag_by_id(owner_browser.contents, 'nonempty')
166 >>> print extract_text(tag.renderContents())176 >>> print extract_text(tag.renderContents())
167 Foo uses the following custom language codes:177 Foo uses the following custom language codes:
168 Code... ...maps to language178 Code... ...maps to language
169 no Norwegian Nynorsk179 no Norwegian Nynorsk
170180
171 >>> owner_browser.getLink("no").click()181 >>> user_browser.getLink("no").click()
172 >>> owner_browser.getLink("remove custom language code")182 >>> user_browser.getLink("remove custom language code")
173 Traceback (most recent call last):183 Traceback (most recent call last):
174 ...184 ...
175 LinkNotFoundError185 LinkNotFoundError
176186
177 >>> owner_browser.open(remove_page)187 >>> user_browser.open(remove_page)
178 Traceback (most recent call last):188 Traceback (most recent call last):
179 ...189 ...
180 Unauthorized: ...190 Unauthorized: ...
@@ -188,7 +198,8 @@
188package--i.e. the combination of a distribution and a source package198package--i.e. the combination of a distribution and a source package
189name. However, since there is no Translations page for that type of199name. However, since there is no Translations page for that type of
190object (and we'd probably never go there if there were), the link is200object (and we'd probably never go there if there were), the link is
191shown on the source package page.201shown on the source package page. For distributions, the owner of the
202distribution's translation group is a translations administrator.
192203
193 >>> login(ANONYMOUS)204 >>> login(ANONYMOUS)
194 >>> from lp.registry.model.sourcepackage import SourcePackage205 >>> from lp.registry.model.sourcepackage import SourcePackage
@@ -210,38 +221,44 @@
210 ... distroseries=other_series,221 ... distroseries=other_series,
211 ... sourcepackagename=package.sourcepackagename),222 ... sourcepackagename=package.sourcepackagename),
212 ... rootsite="translations")223 ... rootsite="translations")
224 >>> translations_admin = factory.makePerson(
225 ... email='ta@example.com', password='test')
226 >>> translationgroup = factory.makeTranslationGroup(
227 ... owner=translations_admin)
228 >>> removeSecurityProxy(distro).translationgroup = translationgroup
213 >>> logout()229 >>> logout()
214230
215 >>> admin_browser.open(package_page)231 >>> translations_browser = setupBrowser("Basic ta@example.com:test")
232 >>> translations_browser.open(package_page)
216233
217Of course in this case, the notice about there being no custom language234Of course in this case, the notice about there being no custom language
218codes talks about a package, not a project.235codes talks about a package, not a project.
219236
220 >>> tag = find_custom_language_codes_link(admin_browser)237 >>> tag = find_custom_language_codes_link(translations_browser)
221 >>> print extract_text(tag.renderContents())238 >>> print extract_text(tag.renderContents())
222 If necessary, you may239 If necessary, you may
223 define custom language codes240 define custom language codes
224 for this package.241 for this package.
225242
226 >>> admin_browser.getLink("define custom language codes").click()243 >>> translations_browser.getLink("define custom language codes").click()
227 >>> custom_language_codes_page = admin_browser.url244 >>> custom_language_codes_page = translations_browser.url
228245
229 >>> tag = find_tag_by_id(admin_browser.contents, 'empty')246 >>> tag = find_tag_by_id(translations_browser.contents, 'empty')
230 >>> print extract_text(tag.renderContents())247 >>> print extract_text(tag.renderContents())
231 No custom language codes have been defined.248 No custom language codes have been defined.
232249
233Again, an admin can add a language code.250A translations admin can add a language code.
234251
235 >>> admin_browser.getLink("Add a custom language code").click()252 >>> translations_browser.getLink("Add a custom language code").click()
236 >>> add_page = admin_browser.url253 >>> add_page = translations_browser.url
237254
238 >>> admin_browser.getControl("Language code:").value = 'pt-br'255 >>> translations_browser.getControl("Language code:").value = 'pt-br'
239 >>> admin_browser.getControl("Language:").value = ['pt_BR']256 >>> translations_browser.getControl("Language:").value = ['pt_BR']
240 >>> admin_browser.getControl("Add").click()257 >>> translations_browser.getControl("Add").click()
241258
242The language code is displayed.259The language code is displayed.
243260
244 >>> tag = find_tag_by_id(admin_browser.contents, 'nonempty')261 >>> tag = find_tag_by_id(translations_browser.contents, 'nonempty')
245 >>> print extract_text(tag.renderContents())262 >>> print extract_text(tag.renderContents())
246 bar in distro uses the following custom language codes:263 bar in distro uses the following custom language codes:
247 Code... ...maps to language264 Code... ...maps to language
@@ -250,15 +267,15 @@
250It's also displayed identically on the same package but in another267It's also displayed identically on the same package but in another
251release series of the same distribution.268release series of the same distribution.
252269
253 >>> admin_browser.open(page_in_other_series)270 >>> translations_browser.open(page_in_other_series)
254 >>> tag = find_custom_language_codes_link(admin_browser)271 >>> tag = find_custom_language_codes_link(translations_browser)
255 >>> print extract_text(tag.renderContents())272 >>> print extract_text(tag.renderContents())
256 If necessary, you may273 If necessary, you may
257 define custom language codes274 define custom language codes
258 for this package.275 for this package.
259276
260 >>> admin_browser.getLink("define custom language codes").click()277 >>> translations_browser.getLink("define custom language codes").click()
261 >>> tag = find_tag_by_id(admin_browser.contents, 'nonempty')278 >>> tag = find_tag_by_id(translations_browser.contents, 'nonempty')
262 >>> print extract_text(tag.renderContents())279 >>> print extract_text(tag.renderContents())
263 bar in distro uses the following custom language codes:280 bar in distro uses the following custom language codes:
264 Code... ...maps to language281 Code... ...maps to language
@@ -267,13 +284,13 @@
267284
268The new code has a link there...285The new code has a link there...
269286
270 >>> admin_browser.getLink("pt-br").click()287 >>> translations_browser.getLink("pt-br").click()
271288
272...and can be deleted.289...and can be deleted.
273290
274 >>> admin_browser.getLink("remove custom language code").click()291 >>> translations_browser.getLink("remove custom language code").click()
275 >>> admin_browser.getControl("Remove").click()292 >>> translations_browser.getControl("Remove").click()
276293
277 >>> tag = find_tag_by_id(admin_browser.contents, 'empty')294 >>> tag = find_tag_by_id(translations_browser.contents, 'empty')
278 >>> print extract_text(tag.renderContents())295 >>> print extract_text(tag.renderContents())
279 No custom language codes have been defined.296 No custom language codes have been defined.
280297
=== modified file 'lib/lp/translations/templates/customlanguagecode-index.pt'
--- lib/lp/translations/templates/customlanguagecode-index.pt 2010-08-20 01:41:58 +0000
+++ lib/lp/translations/templates/customlanguagecode-index.pt 2010-12-23 09:44:44 +0000
@@ -26,7 +26,7 @@
2626
27 <div class="portlet">27 <div class="portlet">
28 <ul class="horizontal">28 <ul class="horizontal">
29 <li tal:condition="context/required:launchpad.Admin">29 <li tal:condition="context/required:launchpad.TranslationsAdmin">
30 <a class="remove sprite"30 <a class="remove sprite"
31 tal:attributes="href context/fmt:url:translations/+remove">31 tal:attributes="href context/fmt:url:translations/+remove">
32 remove custom language code32 remove custom language code
3333
=== added file 'lib/lp/translations/templates/customlanguagecode-remove.pt'
--- lib/lp/translations/templates/customlanguagecode-remove.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/translations/templates/customlanguagecode-remove.pt 2010-12-23 09:44:44 +0000
@@ -0,0 +1,18 @@
1<html
2 xmlns="http://www.w3.org/1999/xhtml"
3 xmlns:tal="http://xml.zope.org/namespaces/tal"
4 xmlns:metal="http://xml.zope.org/namespaces/metal"
5 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
6 metal:use-macro="view/macro:page/main_only"
7 i18n:domain="launchpad">
8 <body>
9 <div metal:fill-slot="main">
10 <div metal:use-macro="context/@@launchpad_form/form">
11 <div metal:fill-slot="extra_info" class="documentDescription">
12 You are going to remove the custom language code
13 '<tal:language-code replace="view/code" />'.
14 </div>
15 </div>
16 </div>
17 </body>
18</html>
019
=== modified file 'lib/lp/translations/templates/customlanguagecodes-index.pt'
--- lib/lp/translations/templates/customlanguagecodes-index.pt 2010-08-20 01:41:58 +0000
+++ lib/lp/translations/templates/customlanguagecodes-index.pt 2010-12-23 09:44:44 +0000
@@ -33,7 +33,8 @@
33 <tr>33 <tr>
34 <th>Code...</th>34 <th>Code...</th>
35 <th>...maps to language</th>35 <th>...maps to language</th>
36 <th tal:condition="context/required:launchpad.Admin"></th>36 <th tal:condition="context/required:launchpad.TranslationsAdmin">
37 </th>
37 </tr>38 </tr>
38 </thead>39 </thead>
39 <tbody>40 <tbody>
@@ -51,7 +52,7 @@
51 &mdash;52 &mdash;
52 </tal:nolanguage>53 </tal:nolanguage>
53 </td>54 </td>
54 <td tal:condition="context/required:launchpad.Admin">55 <td tal:condition="context/required:launchpad.TranslationsAdmin">
55 <a tal:attributes="href entry/fmt:url:translations/+remove"56 <a tal:attributes="href entry/fmt:url:translations/+remove"
56 alt="Remove"57 alt="Remove"
57 title="Remove"58 title="Remove"
@@ -70,7 +71,7 @@
7071
71 <div>72 <div>
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"
73 tal:condition="context/required:launchpad.Admin"74 tal:condition="context/required:launchpad.TranslationsAdmin"
74 class="add sprite">75 class="add sprite">
75 Add a custom language code76 Add a custom language code
76 </a>77 </a>
7778
=== modified file 'lib/lp/translations/templates/product-portlet-translatables.pt'
--- lib/lp/translations/templates/product-portlet-translatables.pt 2010-08-20 00:39:54 +0000
+++ lib/lp/translations/templates/product-portlet-translatables.pt 2010-12-23 09:44:44 +0000
@@ -65,7 +65,7 @@
65</div>65</div>
6666
67<div class="portlet"67<div class="portlet"
68 tal:condition="context/required:launchpad.Admin"68 tal:condition="context/required:launchpad.TranslationsAdmin"
69 id="custom-language-codes">69 id="custom-language-codes">
70 If necessary, you may70 If necessary, you may
71 <a tal:attributes="href context/fmt:url:translations/+custom-language-codes"71 <a tal:attributes="href context/fmt:url:translations/+custom-language-codes"
7272
=== modified file 'lib/lp/translations/templates/sourcepackage-translations.pt'
--- lib/lp/translations/templates/sourcepackage-translations.pt 2010-10-10 21:54:16 +0000
+++ lib/lp/translations/templates/sourcepackage-translations.pt 2010-12-23 09:44:44 +0000
@@ -39,7 +39,7 @@
39 <a tal:attributes="href context/menu:navigation/download/url">39 <a tal:attributes="href context/menu:navigation/download/url">
40 download a full tarball</a> with translations.40 download a full tarball</a> with translations.
41 </p>41 </p>
42 <p tal:condition="context/required:launchpad.Admin"42 <p tal:condition="context/distribution_sourcepackage/required:launchpad.TranslationsAdmin"
43 id="custom-language-codes">43 id="custom-language-codes">
44 If necessary, you may44 If necessary, you may
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"