Merge lp:~edwin-grubbs/launchpad/bug-297833-invite-private-team into lp:launchpad/db-devel

Proposed by Edwin Grubbs
Status: Merged
Approved by: Curtis Hovey
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~edwin-grubbs/launchpad/bug-297833-invite-private-team
Merge into: lp:launchpad/db-devel
Diff against target: 1180 lines (+371/-101)
44 files modified
lib/canonical/launchpad/fields/__init__.py (+36/-12)
lib/lp/archiveuploader/nascentuploadfile.py (+1/-1)
lib/lp/archiveuploader/tests/nascentuploadfile.txt (+71/-0)
lib/lp/bugs/templates/bug-portlet-subscribers.pt (+1/-1)
lib/lp/bugs/templates/bugtarget-filebug-submit-bug.pt (+1/-1)
lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt (+1/-1)
lib/lp/bugs/templates/bugtarget-portlet-bugtags.pt (+1/-1)
lib/lp/bugs/templates/bugtask-index.pt (+3/-3)
lib/lp/bugs/templates/bugtask-tasks-and-nominations-table-row.pt (+1/-1)
lib/lp/bugs/templates/bugtasks-and-nominations-table.pt (+1/-1)
lib/lp/bugs/templates/official-bug-target-manage-tags.pt (+1/-1)
lib/lp/code/templates/branch-import-details.pt (+1/-1)
lib/lp/code/templates/branch-index.pt (+1/-1)
lib/lp/code/templates/branch-listing.pt (+1/-1)
lib/lp/code/templates/branch-portlet-subscribers.pt (+1/-1)
lib/lp/code/templates/branch-related-bugs-specs.pt (+1/-1)
lib/lp/code/templates/branchmergeproposal-generic-listing.pt (+1/-1)
lib/lp/registry/browser/person.py (+4/-0)
lib/lp/registry/browser/tests/productrelease-views.txt (+1/-1)
lib/lp/registry/browser/tests/team-views.txt (+27/-0)
lib/lp/registry/interfaces/person.py (+4/-6)
lib/lp/registry/stories/teammembership/xx-private-membership.txt (+6/-5)
lib/lp/registry/templates/object-timeline-graph.pt (+1/-1)
lib/lp/registry/templates/person-macros.pt (+1/-1)
lib/lp/registry/templates/product-new.pt (+1/-1)
lib/lp/registry/templates/productrelease-add-from-series.pt (+1/-1)
lib/lp/registry/templates/team-add-my-teams.pt (+2/-0)
lib/lp/registry/templates/teammembership-index.pt (+1/-1)
lib/lp/registry/templates/timeline-macros.pt (+1/-1)
lib/lp/soyuz/templates/archive-edit-dependencies.pt (+1/-1)
lib/lp/soyuz/templates/archive-macros.pt (+1/-1)
lib/lp/soyuz/templates/archive-packages.pt (+1/-1)
lib/lp/soyuz/templates/archive-subscribers.pt (+1/-1)
lib/lp/translations/browser/language.py (+8/-0)
lib/lp/translations/stories/distroseries/xx-distroseries-templates.txt (+56/-13)
lib/lp/translations/stories/productseries/xx-productseries-templates.txt (+27/-18)
lib/lp/translations/stories/standalone/xx-language.txt (+68/-7)
lib/lp/translations/templates/language-index.pt (+9/-4)
lib/lp/translations/templates/object-templates.pt (+20/-3)
lib/lp/translations/templates/pofile-export.pt (+1/-1)
lib/lp/translations/templates/pofile-translate.pt (+1/-1)
lib/lp/translations/templates/translation-import-queue-macros.pt (+1/-1)
lib/lp/translations/templates/translationimportqueueentry-index.pt (+1/-1)
lib/lp/translations/templates/translationmessage-translate.pt (+1/-1)
To merge this branch: bzr merge lp:~edwin-grubbs/launchpad/bug-297833-invite-private-team
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Michael Hudson-Doyle Abstain
Review via email: mp+15258@code.launchpad.net

Commit message

Improved error message when trying to add a private team as a member of another team.

To post a comment you must log in.
Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :

Summary
-------

This branch makes the "Constraint not satisfied" error message
clearer when someone tries to add a private team as a member
of another team.

I also noticed that the $team/+add-my-teams would OOPS if you
tried to add a private team as a member. I filtered out private
teams from the list of checkboxes, so that the error could not
occur on this page.

Tests
-----

./bin/test -vv -t team-views.txt

Demo and Q/A
------------

* Open http://launchpad.dev/~guadamen/+addmember
  * Enter "myteam" and click "Add member".
  * The error message should be:
    "A private team cannot become a member of another team."

* Open https://launchpad.dev/people/+newteam
  * Add a new team with the visibility set to private.
  * Open https://launchpad.dev/~guadamen/+add-my-teams
  * The newly created team should not appear in this list.

Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

Putting "<p>Private teams cannot be added as a member of another team.</p>" on ever +add-my-teams page seems a bit crass. I also can't reproduce this problem running from trunk :/

The fix for +addmember is good. It's a bit of a shame the fix has to be at this level, but I don't think there's a choice about this (AFAIK no way for a vocabulary to say "no this thing isn't in this vocabulary *and here's why*).

Abstaining overall, sorry.

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

Hi Edwin.

Thanks for providing a fix for this situation. Brad and I discussed the issue of allowing true private teams to be members today. It is still a security nightmare. There is no easy fix, and the Launchpad team is not investing in fixing this in the next 6 months. I think your approach is correct, but I have a concern about the test on line 37--If I am not a member of the private team, I am not allowed to know it exists. This helpful warning will allow me to guess the existence of team names by trying to make them members.

Launchpad shows a 404 for teams that you cannot know. In this situation, the correct error is to say the team/person does not exist. If I am a member of the private team, then I may know that the relationship is forbidden.

review: Needs Information (code)
Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :
Download full text (9.8 KiB)

Hi Curtis,

Thanks for the review.

> Hi Edwin.
>
> Thanks for providing a fix for this situation. Brad and I discussed the issue
> of allowing true private teams to be members today. It is still a security
> nightmare. There is no easy fix, and the Launchpad team is not investing in
> fixing this in the next 6 months. I think your approach is correct, but I have
> a concern about the test on line 37--If I am not a member of the private team,
> I am not allowed to know it exists. This helpful warning will allow me to
> guess the existence of team names by trying to make them members.
>
> Launchpad shows a 404 for teams that you cannot know. In this situation, the
> correct error is to say the team/person does not exist. If I am a member of
> the private team, then I may know that the relationship is forbidden.

I moved the check from the view class into the PublicPersonChoice and ParticipatingPersonChoice classes. This solves the leak, since the Choice classes constraint() is never called if the widget can't look up the team in the vocabulary, and the vocabulary filters out all private-membership teams and any private teams that the user is not a member of. Therefore, the possible error messages are:

 * "Invalid value" when the user can't know about a private team. Private membership teams will also cause this error, since they are not in the vocab. There is some redundancy here with respect to the error message in ParticipatingPersonChoice, but I didn't worry about that since you said private-membership teams are going away.

 * "A private team isnot allowed" when the user enters a private team that he belongs to. By allowing private teams in the vocabulary, we avoid the user being confused when they can't find a team in the picker that they know exists. beuno had mentioned that it would be nice if the picker showed such invalid options, but didn't let the user actually select them. I avoided the extremely verbose but correct error message "Private & private-membership teams are not allowed."

 * "A private-membership team is not allowed" when the user enters a private-membership team in a field that allows completely private teams, such as the project series' driver.

Here is the incremental diff:
{{{
=== modified file 'lib/canonical/launchpad/fields/__init__.py'
--- lib/canonical/launchpad/fields/__init__.py 2009-11-23 03:43:05 +0000
+++ lib/canonical/launchpad/fields/__init__.py 2009-12-01 22:08:45 +0000
@@ -37,6 +37,8 @@
     'PasswordField',
     'PillarAliases',
     'PillarNameField',
+ 'PrivateMembershipTeamNotAllowed',
+ 'PrivateTeamNotAllowed',
     'ProductBugTracker',
     'ProductNameField',
     'PublicPersonChoice',
@@ -758,7 +760,7 @@
     """Return True if the person is public."""
     from canonical.launchpad.interfaces import IPerson, PersonVisibility
     if not IPerson.providedBy(person):
- raise ConstraintNotSatisfied("Expected a person.")
+ return False
     if person.visibility == PersonVisibility.PUBLIC:
         return True
     else:
@@ -770,7 +772,7 @@
     """True if the person/team has private membership visibility."""
     from canonical.launchpad.interfaces import IPerson, PersonVisibili...

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

Thanks Edwin.

This s a great re-implementation. This is good to land when PQM opens.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/fields/__init__.py'
2--- lib/canonical/launchpad/fields/__init__.py 2009-11-23 03:43:05 +0000
3+++ lib/canonical/launchpad/fields/__init__.py 2009-12-07 21:11:12 +0000
4@@ -37,6 +37,8 @@
5 'PasswordField',
6 'PillarAliases',
7 'PillarNameField',
8+ 'PrivateMembershipTeamNotAllowed',
9+ 'PrivateTeamNotAllowed',
10 'ProductBugTracker',
11 'ProductNameField',
12 'PublicPersonChoice',
13@@ -758,7 +760,7 @@
14 """Return True if the person is public."""
15 from canonical.launchpad.interfaces import IPerson, PersonVisibility
16 if not IPerson.providedBy(person):
17- raise ConstraintNotSatisfied("Expected a person.")
18+ return False
19 if person.visibility == PersonVisibility.PUBLIC:
20 return True
21 else:
22@@ -770,7 +772,7 @@
23 """True if the person/team has private membership visibility."""
24 from canonical.launchpad.interfaces import IPerson, PersonVisibility
25 if not IPerson.providedBy(person):
26- raise ConstraintNotSatisfied("Expected a person.")
27+ return False
28 if person.visibility == PersonVisibility.PRIVATE_MEMBERSHIP:
29 # PRIVATE_MEMBERSHIP.
30 return True
31@@ -779,16 +781,36 @@
32 return False
33
34
35-class PublicPersonChoice(Choice):
36+class PrivateTeamNotAllowed(ConstraintNotSatisfied):
37+ __doc__ = _("A private team is not allowed.")
38+
39+
40+class PrivateMembershipTeamNotAllowed(ConstraintNotSatisfied):
41+ __doc__ = _("A private-membership team is not allowed.")
42+
43+
44+class PersonChoice(Choice):
45+ """A person or team.
46+
47+ This is useful as a superclass and provides a clearer error message than
48+ "Constraint not satisfied".
49+ """
50+ implements(IReferenceChoice)
51+ schema = IObject # Will be set to IPerson once IPerson is defined.
52+
53+
54+class PublicPersonChoice(PersonChoice):
55 """A person or team who is public."""
56- implements(IReferenceChoice)
57- schema = IObject # Will be set to IPerson once IPerson is defined.
58
59 def constraint(self, value):
60- return is_valid_public_person(value)
61-
62-
63-class ParticipatingPersonChoice(Choice):
64+ if is_valid_public_person(value):
65+ return True
66+ else:
67+ # The vocabulary prevents the revealing of private team names.
68+ raise PrivateTeamNotAllowed(value)
69+
70+
71+class ParticipatingPersonChoice(PersonChoice):
72 """A person or team who is not a private membership team.
73
74 A person can participate in all contexts. A PRIVATE team can participate
75@@ -796,8 +818,10 @@
76 user. A PRIVATE MEMBERSHIP team is severely limited in the roles in which
77 it can participate.
78 """
79- implements(IReferenceChoice)
80- schema = IObject # Will be set to IPerson once IPerson is defined.
81
82 def constraint(self, value):
83- return not is_private_membership(value)
84+ if not is_private_membership(value):
85+ return True
86+ else:
87+ # The vocabulary prevents the revealing of private team names.
88+ raise PrivateMembershipTeamNotAllowed(value)
89
90=== modified file 'lib/lp/archiveuploader/nascentuploadfile.py'
91--- lib/lp/archiveuploader/nascentuploadfile.py 2009-11-10 13:09:26 +0000
92+++ lib/lp/archiveuploader/nascentuploadfile.py 2009-12-07 21:11:12 +0000
93@@ -690,7 +690,7 @@
94 tar_checker.ancient_files[first_file])
95 yield UploadError(
96 "%s: has %s file(s) with a time stamp too "
97- "far into the future (e.g. %s [%s])."
98+ "far in the past (e.g. %s [%s])."
99 % (self.filename, len(ancient_files), first_file,
100 timestamp))
101 return
102
103=== modified file 'lib/lp/archiveuploader/tests/nascentuploadfile.txt'
104--- lib/lp/archiveuploader/tests/nascentuploadfile.txt 2009-07-08 08:38:05 +0000
105+++ lib/lp/archiveuploader/tests/nascentuploadfile.txt 2009-12-07 21:11:12 +0000
106@@ -539,6 +539,77 @@
107
108 == DebBinaryUploadFile ==
109
110+DebBinaryUploadFile models a binary .deb file.
111+
112+ >>> from lp.archiveuploader.nascentuploadfile import (
113+ ... DebBinaryUploadFile)
114+ >>> ed_deb_path = datadir('ed_0.2-20_i386.deb')
115+ >>> ed_binary_deb = DebBinaryUploadFile(ed_deb_path,
116+ ... 'e31eeb0b6b3b87e1ea79378df864ffff',
117+ ... 15, 'main/editors', 'important', 'foo', '1.2',
118+ ... ed_mixed_changes, modified_insecure_policy,
119+ ... mock_logger_quiet)
120+
121+Like the other files it can be verified:
122+
123+ >>> list(ed_binary_deb.verify())
124+ []
125+
126+Verification checks that the specified section matches the section in the
127+changes file:
128+
129+ >>> ed_binary_deb = DebBinaryUploadFile(ed_deb_path,
130+ ... 'e31eeb0b6b3b87e1ea79378df864ffff',
131+ ... 15, 'main/net', 'important', 'foo', '1.2',
132+ ... ed_mixed_changes, modified_insecure_policy,
133+ ... mock_logger_quiet)
134+ >>> list(ed_binary_deb.verify())
135+ [UploadError('ed_0.2-20_i386.deb control file lists section as
136+ main/editors but changes file has main/net.',)]
137+
138+It also checks the priority against the changes file:
139+
140+ >>> ed_binary_deb = DebBinaryUploadFile(ed_deb_path,
141+ ... 'e31eeb0b6b3b87e1ea79378df864ffff',
142+ ... 15, 'main/editors', 'extra', 'foo', '1.2',
143+ ... ed_mixed_changes, modified_insecure_policy,
144+ ... mock_logger_quiet)
145+ >>> list(ed_binary_deb.verify())
146+ [UploadError('ed_0.2-20_i386.deb control file lists priority as important
147+ but changes file has extra.',)]
148+
149+The timestamp of the files in the .deb are tested against the policy for being
150+too new:
151+
152+ >>> old_only_policy = getPolicy(
153+ ... name='insecure', distro='ubuntu', distroseries='hoary')
154+ >>> old_only_policy.can_upload_binaries = True
155+ >>> old_only_policy.future_time_grace = -5 * 365 * 24 * 60 * 60
156+
157+ >>> ed_binary_deb = DebBinaryUploadFile(ed_deb_path,
158+ ... 'e31eeb0b6b3b87e1ea79378df864ffff',
159+ ... 15, 'main/editors', 'important', 'foo', '1.2',
160+ ... ed_mixed_changes, old_only_policy,
161+ ... mock_logger_quiet)
162+ >>> list(ed_binary_deb.verifyDebTimestamp())
163+ [UploadError('ed_0.2-20_i386.deb: has 26 file(s) with a time stamp too
164+ far into the future (e.g. control [Thu Jan 3 19:29:01 2008]).',)]
165+
166+... as well as for being too old:
167+
168+ >>> new_only_policy = getPolicy(
169+ ... name='insecure', distro='ubuntu', distroseries='hoary')
170+ >>> new_only_policy.can_upload_binaries = True
171+ >>> new_only_policy.earliest_year = 2010
172+ >>> ed_binary_deb = DebBinaryUploadFile(ed_deb_path,
173+ ... 'e31eeb0b6b3b87e1ea79378df864ffff',
174+ ... 15, 'main/editors', 'important', 'foo', '1.2',
175+ ... ed_mixed_changes, new_only_policy,
176+ ... mock_logger_quiet)
177+ >>> list(ed_binary_deb.verify())
178+ [UploadError('ed_0.2-20_i386.deb: has 26 file(s) with a time stamp too
179+ far in the past (e.g. control [Thu Jan 3 19:29:01 2008]).',)]
180+
181
182 == UDebBinaryUploadFile ==
183
184
185=== modified file 'lib/lp/bugs/templates/bug-portlet-subscribers.pt'
186--- lib/lp/bugs/templates/bug-portlet-subscribers.pt 2009-11-26 03:13:32 +0000
187+++ lib/lp/bugs/templates/bug-portlet-subscribers.pt 2009-12-07 21:11:12 +0000
188@@ -25,7 +25,7 @@
189 <img src="/@@/spinner" />
190 </div>
191 <script type="text/javascript">
192- YUI().use('io-base', 'node', 'bugs.bugtask_index', function(Y) {
193+ LPS.use('io-base', 'node', 'bugs.bugtask_index', function(Y) {
194 // Must be done inline here to ensure the load event fires.
195 // This is a work around for a YUI3 issue with event handling.
196 var subscription_link = Y.one('.menu-link-subscription');
197
198=== modified file 'lib/lp/bugs/templates/bugtarget-filebug-submit-bug.pt'
199--- lib/lp/bugs/templates/bugtarget-filebug-submit-bug.pt 2009-10-01 12:09:37 +0000
200+++ lib/lp/bugs/templates/bugtarget-filebug-submit-bug.pt 2009-12-07 21:11:12 +0000
201@@ -14,7 +14,7 @@
202 tal:define="lp_js string:${icingroot}/build"
203 tal:attributes="src string:${lp_js}/bugs/filebug-dupefinder.js"></script>
204 <script type="text/javascript">
205- YUI().use('base', 'node', 'oop', 'event', 'bugs.dupe_finder', function(Y) {
206+ LPS.use('base', 'node', 'oop', 'event', 'bugs.dupe_finder', function(Y) {
207 Y.bugs.setup_dupe_finder();
208 });
209 </script>
210
211=== modified file 'lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt'
212--- lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt 2009-11-04 13:56:17 +0000
213+++ lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt 2009-12-07 21:11:12 +0000
214@@ -12,7 +12,7 @@
215 <img src="/@@/spinner" />
216 </div>
217 <script type="text/javascript">
218- YUI().use('io-base', 'node', function(Y) {
219+ LPS.use('io-base', 'node', function(Y) {
220 Y.on('domready', function() {
221 var portlet = Y.one('#portlet-bugfilters');
222 Y.one('#bugfilters-portlet-spinner').setStyle('display', 'block');
223
224=== modified file 'lib/lp/bugs/templates/bugtarget-portlet-bugtags.pt'
225--- lib/lp/bugs/templates/bugtarget-portlet-bugtags.pt 2009-11-04 13:56:17 +0000
226+++ lib/lp/bugs/templates/bugtarget-portlet-bugtags.pt 2009-12-07 21:11:12 +0000
227@@ -9,7 +9,7 @@
228 <a id="tags-content-link"
229 tal:attributes="href context/fmt:url/+bugtarget-portlet-tags-content"></a>
230 <script type="text/javascript">
231- YUI().use('io-base', 'node', function(Y) {
232+ LPS.use('io-base', 'node', function(Y) {
233 Y.on('domready', function() {
234 Y.one('#tags-portlet-spinner').setStyle('display', 'block');
235
236
237=== modified file 'lib/lp/bugs/templates/bugtask-index.pt'
238--- lib/lp/bugs/templates/bugtask-index.pt 2009-11-30 17:57:15 +0000
239+++ lib/lp/bugs/templates/bugtask-index.pt 2009-12-07 21:11:12 +0000
240@@ -37,7 +37,7 @@
241 </script>
242 </tal:devmode>
243 <script type="text/javascript">
244- YUI().use('base', 'node', 'oop', 'event', 'bugs.bugtask_index',
245+ LPS.use('base', 'node', 'oop', 'event', 'bugs.bugtask_index',
246 'code.branchmergeproposal.popupdiff', function(Y) {
247 Y.bugs.setup_bugtask_index();
248 Y.on('load', function(e) {
249@@ -155,7 +155,7 @@
250 <img src="/@@/spinner" id="tags-edit-spinner" style="display: none" />
251 <a href="+edit" title="Edit tags" id="edit-tags-trigger" class="sprite edit"></a>
252 <script type="text/javascript">
253- YUI().use('event', 'node', 'bugs.bug_tags_entry', function(Y) {
254+ LPS.use('event', 'node', 'bugs.bug_tags_entry', function(Y) {
255 // XXX intellectronica 2009-04-16 bug #362309:
256 // The load event fires very late on bug pages that take a
257 // long time to render, but we prefer to use it since the
258@@ -295,7 +295,7 @@
259 button.style.display = 'none';
260 </script>
261 <script type="text/javascript">
262- YUI().use('lp.comment', function(Y) {
263+ LPS.use('lp.comment', function(Y) {
264 var comment = new Y.lp.Comment();
265 comment.render();
266 });
267
268=== modified file 'lib/lp/bugs/templates/bugtask-tasks-and-nominations-table-row.pt'
269--- lib/lp/bugs/templates/bugtask-tasks-and-nominations-table-row.pt 2009-11-03 15:32:31 +0000
270+++ lib/lp/bugs/templates/bugtask-tasks-and-nominations-table-row.pt 2009-12-07 21:11:12 +0000
271@@ -185,7 +185,7 @@
272 class="bugtasks-table-row-init-script"
273 tal:condition="not:view/many_bugtasks"
274 tal:content="string:
275- YUI().use('event', 'bugs.bugtask_index', function(Y) {
276+ LPS.use('event', 'bugs.bugtask_index', function(Y) {
277 Y.on('load',
278 function(e) {
279 Y.bugs.setup_bugtask_row(${view/js_config});
280
281=== modified file 'lib/lp/bugs/templates/bugtasks-and-nominations-table.pt'
282--- lib/lp/bugs/templates/bugtasks-and-nominations-table.pt 2009-09-02 22:13:06 +0000
283+++ lib/lp/bugs/templates/bugtasks-and-nominations-table.pt 2009-12-07 21:11:12 +0000
284@@ -88,7 +88,7 @@
285 </span>
286
287 <script type="text/javascript" tal:content="string:
288- YUI().use('event', 'bugs.bugtask_index', function(Y) {
289+ LPS.use('event', 'bugs.bugtask_index', function(Y) {
290 Y.on('load', function(e) {
291 Y.bugs.setup_me_too(${view/current_user_affected_js_status});
292 }, window);
293
294=== modified file 'lib/lp/bugs/templates/official-bug-target-manage-tags.pt'
295--- lib/lp/bugs/templates/official-bug-target-manage-tags.pt 2009-09-04 17:03:00 +0000
296+++ lib/lp/bugs/templates/official-bug-target-manage-tags.pt 2009-12-07 21:11:12 +0000
297@@ -31,7 +31,7 @@
298 </script>
299 <script tal:replace="structure view/tags_js_data" />
300 <script type="text/javascript">
301- YUI().use('event', 'bugs.official_bug_tag_management', function(Y) {
302+ LPS.use('event', 'bugs.official_bug_tag_management', function(Y) {
303 Y.on('domready', function(e) {
304 Y.bugs.setup_official_bug_tag_management();
305 });
306
307=== modified file 'lib/lp/code/templates/branch-import-details.pt'
308--- lib/lp/code/templates/branch-import-details.pt 2009-11-04 13:56:17 +0000
309+++ lib/lp/code/templates/branch-import-details.pt 2009-12-07 21:11:12 +0000
310@@ -32,7 +32,7 @@
311 Try again
312 </a>
313 <script type="text/javascript">
314- YUI().use('event', 'node', function(Y) {
315+ LPS.use('event', 'node', function(Y) {
316 Y.on("domready", function () { Y.one('#tryagainlink').setStyle('display', 'inline') });
317 });
318 </script>
319
320=== modified file 'lib/lp/code/templates/branch-index.pt'
321--- lib/lp/code/templates/branch-index.pt 2009-11-17 05:07:41 +0000
322+++ lib/lp/code/templates/branch-index.pt 2009-12-07 21:11:12 +0000
323@@ -47,7 +47,7 @@
324 </tal:devmode>
325 <script type="text/javascript"
326 tal:content="string:
327- YUI().use('node', 'event', 'widget', 'plugin', 'overlay',
328+ LPS.use('node', 'event', 'widget', 'plugin', 'overlay',
329 'lazr.choiceedit', 'code.branchstatus',
330 'code.branchmergeproposal.popupdiff',
331 function(Y) {
332
333=== modified file 'lib/lp/code/templates/branch-listing.pt'
334--- lib/lp/code/templates/branch-listing.pt 2009-11-04 13:56:17 +0000
335+++ lib/lp/code/templates/branch-listing.pt 2009-12-07 21:11:12 +0000
336@@ -41,7 +41,7 @@
337 }
338 registerLaunchpadFunction(hookUpFilterSubmission);
339
340-YUI().use('io-base', 'node', 'json-parse', function(Y) {
341+LPS.use('io-base', 'node', 'json-parse', function(Y) {
342
343 function doUpdate(transaction_id, response, args) {
344 json_values = Y.JSON.parse(response.responseText);
345
346=== modified file 'lib/lp/code/templates/branch-portlet-subscribers.pt'
347--- lib/lp/code/templates/branch-portlet-subscribers.pt 2009-11-04 13:56:17 +0000
348+++ lib/lp/code/templates/branch-portlet-subscribers.pt 2009-12-07 21:11:12 +0000
349@@ -41,7 +41,7 @@
350 string:&lt;script id='milestone-script' type='text/javascript'&gt;" />
351 <!--
352
353- YUI().use('io-base', 'node', 'code.branchsubscription', function(Y) {
354+ LPS.use('io-base', 'node', 'code.branchsubscription', function(Y) {
355
356 if(Y.UA.ie) {
357 Y.one('#subscriber-list').set('innerHTML',
358
359=== modified file 'lib/lp/code/templates/branch-related-bugs-specs.pt'
360--- lib/lp/code/templates/branch-related-bugs-specs.pt 2009-09-08 21:42:45 +0000
361+++ lib/lp/code/templates/branch-related-bugs-specs.pt 2009-12-07 21:11:12 +0000
362@@ -42,7 +42,7 @@
363 string:&lt;script id='branchlink-script' type='text/javascript'&gt;" />
364 <!--
365
366- YUI().use('io-base', 'code.branchlinks', function(Y) {
367+ LPS.use('io-base', 'code.branchlinks', function(Y) {
368
369 if(Y.UA.ie) {
370 return;
371
372=== modified file 'lib/lp/code/templates/branchmergeproposal-generic-listing.pt'
373--- lib/lp/code/templates/branchmergeproposal-generic-listing.pt 2009-11-04 13:56:17 +0000
374+++ lib/lp/code/templates/branchmergeproposal-generic-listing.pt 2009-12-07 21:11:12 +0000
375@@ -24,7 +24,7 @@
376 </form>
377 <script type="text/javascript">
378
379-YUI().use('node', function(Y) {
380+LPS.use('node', function(Y) {
381
382 function submit_filter() {
383 Y.one('#filter_form').submit();
384
385=== modified file 'lib/lp/registry/browser/person.py'
386--- lib/lp/registry/browser/person.py 2009-11-21 14:45:26 +0000
387+++ lib/lp/registry/browser/person.py 2009-12-07 21:11:12 +0000
388@@ -4114,9 +4114,13 @@
389 for team in self.user.getAdministratedTeams():
390 if team == self.context:
391 continue
392+ elif team.visibility != PersonVisibility.PUBLIC:
393+ continue
394 elif team in self.context.activemembers:
395+ # The team is already a member of the context object.
396 continue
397 elif self.context.hasParticipationEntryFor(team):
398+ # The context object is a member/submember of the team.
399 continue
400 candidates.append(team)
401 return candidates
402
403=== modified file 'lib/lp/registry/browser/tests/productrelease-views.txt'
404--- lib/lp/registry/browser/tests/productrelease-views.txt 2009-09-11 16:00:24 +0000
405+++ lib/lp/registry/browser/tests/productrelease-views.txt 2009-12-07 21:11:12 +0000
406@@ -130,7 +130,7 @@
407 >>> script = find_tag_by_id(view.render(), 'milestone-script')
408 >>> print script
409 <script id="milestone-script" type="text/javascript">
410- YUI().use(... 'lp.milestoneoverlay'...
411+ LPS.use(... 'lp.milestoneoverlay'...
412 var milestone_form_uri = '.../app/simple/+addmilestone/++form++';
413 var series_uri = '/app/simple';
414 ...
415
416=== modified file 'lib/lp/registry/browser/tests/team-views.txt'
417--- lib/lp/registry/browser/tests/team-views.txt 2009-10-26 21:12:49 +0000
418+++ lib/lp/registry/browser/tests/team-views.txt 2009-12-07 21:11:12 +0000
419@@ -274,6 +274,7 @@
420 >>> from lp.registry.interfaces.person import PersonVisibility
421 >>> login('admin@canonical.com')
422 >>> private_team = factory.makeTeam(
423+ ... owner=sample_person,
424 ... name='private-team', displayname='Private Team',
425 ... visibility=PersonVisibility.PRIVATE)
426
427@@ -286,6 +287,7 @@
428 Private membership teams are also indicated as private.
429
430 >>> private_membership_team = factory.makeTeam(
431+ ... owner=sample_person,
432 ... name='private-membership-team',
433 ... displayname='Private Membership Team',
434 ... visibility=PersonVisibility.PRIVATE_MEMBERSHIP)
435@@ -294,3 +296,28 @@
436 Team membership is viewable by team members
437 >>> print view.visibility_portlet_class
438 portlet private
439+
440+== +add-my-teams ==
441+
442+This page lists the teams that you administer and can add as a member
443+to the current team. Private teams cannot be added as members to another
444+team.
445+
446+ >>> login_person(sample_person)
447+ >>> view = create_initialized_view(guadamen, '+add-my-teams')
448+ >>> for candidate in view.candidate_teams:
449+ ... print candidate.name, candidate.visibility.title
450+ landscape-developers Public
451+ launchpad-users Public
452+
453+Making one of the teams that sample person owns public will cause it
454+to show up.
455+
456+ >>> removeSecurityProxy(private_membership_team).visibility = (
457+ ... PersonVisibility.PUBLIC)
458+ >>> view = create_initialized_view(guadamen, '+add-my-teams')
459+ >>> for candidate in view.candidate_teams:
460+ ... print candidate.name, candidate.visibility.title
461+ landscape-developers Public
462+ launchpad-users Public
463+ private-membership-team Public
464
465=== modified file 'lib/lp/registry/interfaces/person.py'
466--- lib/lp/registry/interfaces/person.py 2009-12-05 13:17:53 +0000
467+++ lib/lp/registry/interfaces/person.py 2009-12-07 21:11:12 +0000
468@@ -65,10 +65,9 @@
469
470 from canonical.database.sqlbase import block_implicit_flushes
471 from canonical.launchpad.fields import (
472- BlacklistableContentNameField, IconImageUpload, LogoImageUpload,
473- MugshotImageUpload, ParticipatingPersonChoice, PasswordField,
474- PublicPersonChoice, StrippedTextLine, is_private_membership,
475- is_valid_public_person)
476+ BlacklistableContentNameField, IconImageUpload, is_private_membership,
477+ is_valid_public_person, LogoImageUpload, MugshotImageUpload,
478+ PasswordField, PersonChoice, PublicPersonChoice, StrippedTextLine)
479 from canonical.launchpad.interfaces.account import AccountStatus, IAccount
480 from canonical.launchpad.interfaces.emailaddress import IEmailAddress
481 from lp.app.interfaces.headings import IRootContext
482@@ -1518,8 +1517,7 @@
483
484 # Set the schemas to the newly defined interface for classes that deferred
485 # doing so when defined.
486-PublicPersonChoice.schema = IPerson
487-ParticipatingPersonChoice.schema = IPerson
488+PersonChoice.schema = IPerson
489
490
491 class INewPersonForm(IPerson):
492
493=== modified file 'lib/lp/registry/stories/teammembership/xx-private-membership.txt'
494--- lib/lp/registry/stories/teammembership/xx-private-membership.txt 2009-10-26 21:12:49 +0000
495+++ lib/lp/registry/stories/teammembership/xx-private-membership.txt 2009-12-07 21:11:12 +0000
496@@ -234,7 +234,8 @@
497 >>> admin_browser.url
498 'http://launchpad.dev/%7Esimple-team/+addmember'
499 >>> get_feedback_messages(admin_browser.contents)
500- [...Constraint not satisfied...
501+ [...There is 1 error...
502+ ...A private team is not allowed.']
503
504 Anonymous users cannot even know that the private membership team even
505 exists.
506@@ -305,7 +306,7 @@
507 >>> admin_browser.url
508 'http://bugs.launchpad.dev/firefox/+bug/1/+addsubscriber'
509 >>> get_feedback_messages(admin_browser.contents)
510- [...Constraint not satisfied...
511+ [...A private-membership team is not allowed...
512
513
514 == Restrict Subscribing to Blueprints ==
515@@ -320,7 +321,7 @@
516 >>> admin_browser.url
517 'http://blueprints.launchpad.dev/firefox/+spec/canvas/+addsubscriber'
518 >>> get_feedback_messages(admin_browser.contents)
519- [...Constraint not satisfied...
520+ [...A private team is not allowed...
521
522
523 == Restrict Appointing a Translator ==
524@@ -336,7 +337,7 @@
525 >>> admin_browser.url
526 'http://translations.launchpad.dev/+groups/testing-translation-team/+appoint'
527 >>> get_feedback_messages(admin_browser.contents)
528- [...Constraint not satisfied...
529+ [...A private team is not allowed...
530
531
532 == Restrict Project Owner ==
533@@ -365,7 +366,7 @@
534 >>> admin_browser.url
535 'http://launchpad.dev/jokosher/+edit-people'
536 >>> get_feedback_messages(admin_browser.contents)
537- [u'There is 1 error.', u'Constraint not satisfied']
538+ [...A private-membership team is not allowed...
539
540
541 == Restrict Product Bug Supervisor ==
542
543=== modified file 'lib/lp/registry/templates/object-timeline-graph.pt'
544--- lib/lp/registry/templates/object-timeline-graph.pt 2009-11-24 09:30:01 +0000
545+++ lib/lp/registry/templates/object-timeline-graph.pt 2009-12-07 21:11:12 +0000
546@@ -32,7 +32,7 @@
547 include_inactive = false;
548 }
549
550- YUI().use('registry.timeline', 'node', function(Y) {
551+ LPS.use('registry.timeline', 'node', function(Y) {
552 Y.on('domready', function(e) {
553 if (Y.UA.ie) {
554 return;
555
556=== modified file 'lib/lp/registry/templates/person-macros.pt'
557--- lib/lp/registry/templates/person-macros.pt 2009-11-04 13:56:17 +0000
558+++ lib/lp/registry/templates/person-macros.pt 2009-12-07 21:11:12 +0000
559@@ -190,7 +190,7 @@
560 condition="private_prefix">
561 <script type="text/javascript"
562 tal:content="string:
563- YUI().use('node', 'event', function(Y) {
564+ LPS.use('node', 'event', function(Y) {
565 // Prepend/remove 'private-' from team name based on visibility
566 // setting. User can choose to edit it back out, if they wish.
567 function visibility_on_change(e) {
568
569=== modified file 'lib/lp/registry/templates/product-new.pt'
570--- lib/lp/registry/templates/product-new.pt 2009-11-04 13:56:17 +0000
571+++ lib/lp/registry/templates/product-new.pt 2009-12-07 21:11:12 +0000
572@@ -14,7 +14,7 @@
573 * details widgets until the user states that the project they are
574 * registering is not a duplicate.
575 */
576-YUI().use('node', 'lazr.effects', function(Y) {
577+LPS.use('node', 'lazr.effects', function(Y) {
578 Y.on('domready', function() {
579 /* These two regexps serve slightly different purposes. The first
580 * finds the leftmost run of valid url characters for the autofill
581
582=== modified file 'lib/lp/registry/templates/productrelease-add-from-series.pt'
583--- lib/lp/registry/templates/productrelease-add-from-series.pt 2009-11-04 13:56:17 +0000
584+++ lib/lp/registry/templates/productrelease-add-from-series.pt 2009-12-07 21:11:12 +0000
585@@ -14,7 +14,7 @@
586 <tal:script
587 replace="structure
588 string:&lt;script id='milestone-script' type='text/javascript'&gt;" />
589- YUI().use('node', 'lp.milestoneoverlay', function (Y) {
590+ LPS.use('node', 'lp.milestoneoverlay', function (Y) {
591
592 // This is a value for the SELECT OPTION which is passed with
593 // the SELECT's "change" event. It includes some symbols that are not
594
595=== modified file 'lib/lp/registry/templates/team-add-my-teams.pt'
596--- lib/lp/registry/templates/team-add-my-teams.pt 2009-09-02 18:12:57 +0000
597+++ lib/lp/registry/templates/team-add-my-teams.pt 2009-12-07 21:11:12 +0000
598@@ -8,6 +8,8 @@
599 >
600 <body>
601 <div metal:fill-slot="main">
602+ <p>Private teams cannot be added as a member of another team.</p>
603+
604 <p id="no-candidates" tal:condition="not: view/candidate_teams">
605 None of the teams you administer can be added to this team.
606 </p>
607
608=== modified file 'lib/lp/registry/templates/teammembership-index.pt'
609--- lib/lp/registry/templates/teammembership-index.pt 2009-11-04 13:56:17 +0000
610+++ lib/lp/registry/templates/teammembership-index.pt 2009-12-07 21:11:12 +0000
611@@ -20,7 +20,7 @@
612 use-macro="context/@@launchpad_widget_macros/yui2calendar-dependencies" />
613
614 <script type="text/javascript">
615- YUI().use('node', 'lp.calendar', function(Y) {
616+ LPS.use('node', 'lp.calendar', function(Y) {
617 // Ensure that when the picker is used the radio button switches
618 // from 'Never' to 'On' and the expiry field is enabled.
619 Y.on("available", function(e) {
620
621=== modified file 'lib/lp/registry/templates/timeline-macros.pt'
622--- lib/lp/registry/templates/timeline-macros.pt 2009-11-04 13:56:17 +0000
623+++ lib/lp/registry/templates/timeline-macros.pt 2009-12-07 21:11:12 +0000
624@@ -35,7 +35,7 @@
625 if (auto_resize == 'true') {
626 timeline_url += 'resize_frame=timeline-iframe&';
627 }
628- YUI().use('node', function(Y) {
629+ LPS.use('node', function(Y) {
630 if (Y.UA.ie) {
631 return;
632 }
633
634=== modified file 'lib/lp/soyuz/templates/archive-edit-dependencies.pt'
635--- lib/lp/soyuz/templates/archive-edit-dependencies.pt 2009-11-12 17:26:17 +0000
636+++ lib/lp/soyuz/templates/archive-edit-dependencies.pt 2009-12-07 21:11:12 +0000
637@@ -62,7 +62,7 @@
638 </div> <!-- launchpad_form -->
639
640 <script type="text/javascript">
641- YUI().use("node", function(Y) {
642+ LPS.use("node", function(Y) {
643
644 // Highlight (setting bold font-weight) the label for the
645 // selected option in a given NodesList. Assumes the input is
646
647=== modified file 'lib/lp/soyuz/templates/archive-macros.pt'
648--- lib/lp/soyuz/templates/archive-macros.pt 2009-11-04 19:59:16 +0000
649+++ lib/lp/soyuz/templates/archive-macros.pt 2009-12-07 21:11:12 +0000
650@@ -10,7 +10,7 @@
651 </tal:comment>
652
653 <script type="text/javascript">
654-YUI().use('node', 'io-base', 'lazr.anim', 'soyuz-base', function(Y) {
655+LPS.use('node', 'io-base', 'lazr.anim', 'soyuz-base', function(Y) {
656
657
658 /*
659
660=== modified file 'lib/lp/soyuz/templates/archive-packages.pt'
661--- lib/lp/soyuz/templates/archive-packages.pt 2009-11-04 13:56:17 +0000
662+++ lib/lp/soyuz/templates/archive-packages.pt 2009-12-07 21:11:12 +0000
663@@ -23,7 +23,7 @@
664 </tal:devmode>
665 <script type="text/javascript" id="repository-size-update"
666 tal:condition="view/archive_url">
667-YUI().use('io-base', 'lazr.anim', 'node', 'soyuz-base',
668+LPS.use('io-base', 'lazr.anim', 'node', 'soyuz-base',
669 'soyuz.update_archive_build_statuses', function(Y) {
670
671
672
673=== modified file 'lib/lp/soyuz/templates/archive-subscribers.pt'
674--- lib/lp/soyuz/templates/archive-subscribers.pt 2009-09-29 07:21:40 +0000
675+++ lib/lp/soyuz/templates/archive-subscribers.pt 2009-12-07 21:11:12 +0000
676@@ -98,7 +98,7 @@
677 </form>
678 </div><!-- class="portlet" -->
679 <script type="text/javascript" id="setup-archivesubscribers-index">
680- YUI().use('soyuz.archivesubscribers_index', function(Y) {
681+ LPS.use('soyuz.archivesubscribers_index', function(Y) {
682 Y.soyuz.setup_archivesubscribers_index();
683 });
684 </script>
685
686=== modified file 'lib/lp/translations/browser/language.py'
687--- lib/lp/translations/browser/language.py 2009-10-31 11:06:44 +0000
688+++ lib/lp/translations/browser/language.py 2009-12-07 21:11:12 +0000
689@@ -29,6 +29,7 @@
690 enabled_with_permission, GetitemNavigation, LaunchpadEditFormView,
691 LaunchpadFormView, LaunchpadView, Link, NavigationMenu)
692 from lp.translations.utilities.pluralforms import make_friendly_plural_forms
693+from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
694
695 from canonical.widgets import LabeledMultiCheckBoxWidget
696
697@@ -202,6 +203,13 @@
698
699 return pluralforms_list
700
701+ @property
702+ def add_question_url(self):
703+ rosetta = getUtility(ILaunchpadCelebrities).lp_translations
704+ return canonical_url(
705+ rosetta,
706+ view_name='+addquestion',
707+ rootsite='answers')
708
709 class LanguageAdminView(LaunchpadEditFormView):
710 """Handle an admin form submission."""
711
712=== modified file 'lib/lp/translations/stories/distroseries/xx-distroseries-templates.txt'
713--- lib/lp/translations/stories/distroseries/xx-distroseries-templates.txt 2009-10-30 10:09:17 +0000
714+++ lib/lp/translations/stories/distroseries/xx-distroseries-templates.txt 2009-12-07 21:11:12 +0000
715@@ -1,11 +1,15 @@
716-= Templates view for DistroSeries =
717+
718+
719+Templates view for DistroSeries
720+===============================
721
722 The +templates view for DistroSeries gives an overview of the translation
723 templates in this series and provides easy access to the various subpages of
724 each template.
725
726
727-== Getting there ==
728+Getting there
729+-------------
730
731 To get to the listing of all templates, one needs to use the link
732 from the distribution series translations page.
733@@ -16,7 +20,45 @@
734 >>> print user_browser.url
735 http://translations.launchpad.dev/ubuntu/hoary/+templates
736
737-== The templates table ==
738+The templates table
739+-------------------
740+
741+Full template listing for a distribution series is reached by following
742+a link from the distribution series translations page.
743+
744+ >>> anon_browser.open(
745+ ... 'http://translations.launchpad.dev/ubuntu/hoary')
746+ >>> anon_browser.getLink('full list of templates').click()
747+
748+Full listing of templates shows source package name, template name and
749+the date of last update for this distribution series.
750+
751+ >>> table = find_tag_by_id(anon_browser.contents, 'templates_table')
752+ >>> print extract_text(table)
753+ Source package Template name Last update
754+ evolution disabled-template 2007-01-05
755+ evolution evolution-2.2 2005-05-06
756+ evolution man 2006-08-14
757+ mozilla pkgconf-mozilla 2005-05-06
758+ pmount man 2006-08-14
759+ pmount pmount 2005-05-06
760+
761+
762+Logged-in users will see a link from distro series
763+ >>> user_browser.open(
764+ ... 'http://translations.launchpad.dev/ubuntu/hoary')
765+ >>> user_browser.getLink('full list of templates').click()
766+
767+Logged-in users can also choose to download all translations for each
768+of the templates.
769+
770+ >>> table = find_tag_by_id(user_browser.contents, 'templates_table')
771+ >>> print extract_text(table)
772+ Source package Template name Last update Actions
773+ evolution disabled-template 2007-01-05 Download
774+ ...
775+ mozilla pkgconf-mozilla 2005-05-06 Download
776+ ...
777
778 Administrator can see all editing options.
779
780@@ -28,16 +70,17 @@
781
782 >>> table = find_tag_by_id(admin_browser.contents, 'templates_table')
783 >>> print extract_text(table)
784- Source package Template name Actions
785- evolution disabled-template Edit Upload Download Administer
786- evolution evolution-2.2 Edit Upload Download Administer
787- evolution man Edit Upload Download Administer
788- mozilla pkgconf-mozilla Edit Upload Download Administer
789- pmount man Edit Upload Download Administer
790- pmount pmount Edit Upload Download Administer
791-
792-
793-== Links to the templates ==
794+ Source package Template name Last update Actions
795+ evolution disabled-template 2007-01-05 Edit Upload Download Administer
796+ evolution evolution-2.2 2005-05-06 Edit Upload Download Administer
797+ evolution man 2006-08-14 Edit Upload Download Administer
798+ mozilla pkgconf-mozilla 2005-05-06 Edit Upload Download Administer
799+ pmount man 2006-08-14 Edit Upload Download Administer
800+ pmount pmount 2005-05-06 Edit Upload Download Administer
801+
802+
803+Links to the templates
804+----------------------
805
806 Clicking on a template name will take the user to that template's overview
807 page.
808
809=== modified file 'lib/lp/translations/stories/productseries/xx-productseries-templates.txt'
810--- lib/lp/translations/stories/productseries/xx-productseries-templates.txt 2009-10-30 10:09:17 +0000
811+++ lib/lp/translations/stories/productseries/xx-productseries-templates.txt 2009-12-07 21:11:12 +0000
812@@ -1,13 +1,18 @@
813-= Templates view for ProductSeries =
814+
815+
816+Templates view for ProductSeries
817+================================
818
819 The +templates view for ProductSeries gives an overview of the translation
820 templates in this series and provides easy access to the various subpages of
821 each template.
822
823-== Preparation ==
824-
825-To test the ordering of templates in the listing, we need another template
826-that is new but must appear at the top of the list.
827+
828+Preparation
829+-----------
830+
831+To test the ordering of templates in the listing, we need another
832+template that is new but must appear at the top of the list.
833
834 >>> login('foo.bar@canonical.com')
835 >>> from zope.component import getUtility
836@@ -18,7 +23,9 @@
837 ... name='at-the-top')
838 >>> logout()
839
840-== Getting there ==
841+
842+Getting there
843+-------------
844
845 To get to the listing of all templates, one needs to use the link
846 from the product series translations page.
847@@ -30,16 +37,17 @@
848 http://translations.launchpad.dev/evolution/trunk/+templates
849
850
851-== The templates table ==
852+The templates table
853+-------------------
854
855 The page shows a table of all templates and links to their subpages.
856
857 >>> table = find_tag_by_id(user_browser.contents, 'templates_table')
858 >>> print extract_text(table)
859- Template name Actions
860- at-the-top Download
861- evolution-2.2 Download
862- evolution-2.2-test Download
863+ Template name Last update Actions
864+ at-the-top ... Download
865+ evolution-2.2 2005-08-25 Download
866+ evolution-2.2-test 2006-12-13 Download
867
868 If an administrator views this page, links to the templates admin page are
869 shown, too.
870@@ -48,13 +56,14 @@
871 ... 'http://translations.launchpad.dev/evolution/trunk/+templates')
872 >>> table = find_tag_by_id(admin_browser.contents, 'templates_table')
873 >>> print extract_text(table)
874- Template name Actions
875- at-the-top Edit Upload Download Administer
876- evolution-2.2 Edit Upload Download Administer
877- evolution-2.2-test Edit Upload Download Administer
878-
879-
880-== Links to the templates ==
881+ Template name Last update Actions
882+ at-the-top ... Edit Upload Download Administer
883+ evolution-2.2 2005-08-25 Edit Upload Download Administer
884+ evolution-2.2-test 2006-12-13 Edit Upload Download Administer
885+
886+
887+Links to the templates
888+----------------------
889
890 Clicking on a template name will take the user to that template's overview
891 page.
892
893=== modified file 'lib/lp/translations/stories/standalone/xx-language.txt'
894--- lib/lp/translations/stories/standalone/xx-language.txt 2009-10-31 11:06:44 +0000
895+++ lib/lp/translations/stories/standalone/xx-language.txt 2009-12-07 21:11:12 +0000
896@@ -1,6 +1,15 @@
897+
898+
899+Languages view
900+==============
901+
902 Here is the tale of languages. We will see how to create, find and edit
903 them.
904
905+
906+Getting there
907+-------------
908+
909 Launchpad Translations has a main page.
910
911 >>> admin_browser.open('http://translations.launchpad.dev/')
912@@ -11,7 +20,12 @@
913 >>> print admin_browser.url
914 http://translations.launchpad.dev/+languages
915
916-Following the link, there is a form to add new languages.
917+
918+Adding new languages
919+--------------------
920+
921+Following the link from the translations main page, there is a form to
922+add new languages.
923
924 >>> admin_browser.getLink('Add new language').click()
925 >>> print admin_browser.url
926@@ -65,11 +79,16 @@
927 ...
928 LinkNotFoundError
929
930- >>> user_browser.open('http://translations.launchpad.dev/+languages/+add')
931+ >>> user_browser.open(
932+ ... 'http://translations.launchpad.dev/+languages/+add')
933 Traceback (most recent call last):
934 ...
935 Unauthorized:...
936
937+
938+Searching for a language
939+------------------------
940+
941 From the top languages page, anyone can find languages.
942
943 >>> browser.open('http://translations.launchpad.dev/+languages')
944@@ -82,7 +101,11 @@
945 >>> print browser.url
946 http://translations.launchpad.dev/+languages/+index?find=Spanish
947
948-And following one of the found languages, we can see a brief information
949+
950+Read language information
951+-------------------------
952+
953+Following one of the found languages, we can see a brief information
954 about the selected language.
955
956 >>> browser.getLink('Spanish').click()
957@@ -128,14 +151,50 @@
958 ...Uruguay...
959 ...Venezuela...
960
961- >>> topcontributors_portlet = find_portlet(browser.contents, 'Top contributors')
962+ >>> topcontributors_portlet = find_portlet(
963+ ... browser.contents, 'Top contributors')
964 >>> print topcontributors_portlet
965 <...
966 ...Carlos Perelló Marín...
967
968+Our test sample data does not know about plural forms of
969+Abkhazian and about countries where this language is spoken.
970+
971+We will see a note about missing plural forms and a link to Rosetta
972+add question page for informing Rosetta admin about the right plural
973+form.
974+
975+ >>> browser.open('http://translations.launchpad.dev/+languages/ab')
976+ >>> print extract_text(find_portlet(browser.contents, 'Plural forms'
977+ ... ).renderContents())
978+ Plural forms
979+ Unfortunately, Launchpad doesn't know the plural
980+ form information for this language...
981+
982+ >>> print browser.getLink(id='plural_question').url
983+ http://answers.launchpad.dev/rosetta/+addquestion
984+
985+We will see a note that Launchpad does not know in which countries
986+this language is spoken and a link to add question page for informing
987+Rosetta admin about the countries where this page is officially spoken.
988+
989+ >>> countries_portlet = find_portlet(browser.contents, 'Countries')
990+ >>> print countries_portlet
991+ <...
992+ Abkhazian is not registered as being spoken in any
993+ country...
994+
995+ >>> print browser.getLink(id='country_question').url
996+ http://answers.launchpad.dev/rosetta/+addquestion
997+
998+
999+Edit language information
1000+-------------------------
1001+
1002 Finally, there is the edit form to change language basic information.
1003
1004- >>> user_browser.open('http://translations.launchpad.dev/+languages/es')
1005+ >>> user_browser.open(
1006+ ... 'http://translations.launchpad.dev/+languages/es')
1007 >>> print user_browser.url
1008 http://translations.launchpad.dev/+languages/es
1009
1010@@ -146,7 +205,8 @@
1011 ...
1012 LinkNotFoundError
1013
1014- >>> user_browser.open('http://translations.launchpad.dev/+languages/es/+admin')
1015+ >>> user_browser.open(
1016+ ... 'http://translations.launchpad.dev/+languages/es/+admin')
1017 Traceback (most recent call last):
1018 ...
1019 Unauthorized:...
1020@@ -155,7 +215,8 @@
1021
1022 >>> from canonical.launchpad.testing.pages import strip_label
1023
1024- >>> admin_browser.open('http://translations.launchpad.dev/+languages/es')
1025+ >>> admin_browser.open(
1026+ ... 'http://translations.launchpad.dev/+languages/es')
1027 >>> print admin_browser.url
1028 http://translations.launchpad.dev/+languages/es
1029
1030
1031=== modified file 'lib/lp/translations/templates/language-index.pt'
1032--- lib/lp/translations/templates/language-index.pt 2009-09-17 14:45:59 +0000
1033+++ lib/lp/translations/templates/language-index.pt 2009-12-07 21:11:12 +0000
1034@@ -43,8 +43,10 @@
1035 <p class="helpwanted">
1036 Unfortunately, Launchpad doesn't know the plural form
1037 information for this language. If you know it, please open a
1038- <a href="/rosetta/+addticket">ticket</a> with that information,
1039- so we can add it to Launchpad.
1040+ <a id='plural_question'
1041+ tal:attributes="href view/add_question_url"
1042+ >question</a>
1043+ with that information, so we can add it to Launchpad.
1044 </p>
1045 </tal:has_not_pluralforms>
1046 </div>
1047@@ -124,8 +126,11 @@
1048 </tal:language>
1049 is not registered as being spoken in any country. If you know
1050 about a country that officially speaks this language, please
1051- open a <a href="/rosetta/+addticket">ticket</a> with that
1052- information, so we can add it to Launchpad.
1053+ open a
1054+ <a id='country_question'
1055+ tal:attributes="href view/add_question_url"
1056+ >question</a>
1057+ with that information, so we can add it to Launchpad.
1058 </p>
1059 </tal:has_not_countries>
1060 </div>
1061
1062=== modified file 'lib/lp/translations/templates/object-templates.pt'
1063--- lib/lp/translations/templates/object-templates.pt 2009-11-24 19:23:52 +0000
1064+++ lib/lp/translations/templates/object-templates.pt 2009-12-07 21:11:12 +0000
1065@@ -26,16 +26,16 @@
1066 </style>
1067 <style tal:condition="view/is_distroseries" type="text/css">
1068 #templates_table {
1069- width: 72em;
1070+ width: 79em;
1071 }
1072 </style>
1073 <style tal:condition="not:view/is_distroseries" type="text/css">
1074 #templates_table {
1075- width: 50em;
1076+ width: 58em;
1077 }
1078 </style>
1079 <script language="JavaScript" type="text/javascript">
1080- YUI().use('node-base', 'event-delegate', function(Y) {
1081+ LPS.use('node-base', 'event-delegate', function(Y) {
1082 Y.on('domready', function(e) {
1083 Y.all('#templates_table .template_links').addClass(
1084 'inactive_links');
1085@@ -75,6 +75,7 @@
1086 <th tal:condition="view/is_distroseries"
1087 class="sourcepackage_column">Source package</th>
1088 <th class="template_column">Template name</th>
1089+ <th class="lastupdate_column">Last update</th>
1090 <th class="actions_column"
1091 tal:condition="context/required:launchpad.AnyPerson">
1092 Actions</th>
1093@@ -88,6 +89,22 @@
1094 </td>
1095 <td class="template_column"><a tal:attributes="href template/fmt:url"
1096 tal:content="template/name">Template name</a></td>
1097+ <td class="lastupdate_column">
1098+ <span class="sortkey"
1099+ tal:condition="template/date_last_updated"
1100+ tal:content="template/date_last_updated/fmt:datetime">
1101+ time sort key
1102+ </span>
1103+ <span class="lastupdate_column"
1104+ tal:condition="template/date_last_updated"
1105+ tal:attributes="
1106+ title template/date_last_updated/fmt:datetime"
1107+ tal:content="
1108+ template/date_last_updated/fmt:approximatedate"
1109+ >
1110+ 2009-09-23
1111+ </span>
1112+ </td>
1113 <td class="actions_column"
1114 tal:condition="context/required:launchpad.AnyPerson">
1115 <div class="template_links">
1116
1117=== modified file 'lib/lp/translations/templates/pofile-export.pt'
1118--- lib/lp/translations/templates/pofile-export.pt 2009-11-10 21:04:19 +0000
1119+++ lib/lp/translations/templates/pofile-export.pt 2009-12-07 21:11:12 +0000
1120@@ -13,7 +13,7 @@
1121 }
1122 </style>
1123 <script type="text/javascript">
1124- YUI().use('node', 'event', function(Y){
1125+ LPS.use('node', 'event', function(Y){
1126 Y.on('domready', function(){
1127 // The pochanged option is only available for the PO format.
1128 var formatlist = Y.one('#div_format select');
1129
1130=== modified file 'lib/lp/translations/templates/pofile-translate.pt'
1131--- lib/lp/translations/templates/pofile-translate.pt 2009-11-04 19:59:16 +0000
1132+++ lib/lp/translations/templates/pofile-translate.pt 2009-12-07 21:11:12 +0000
1133@@ -20,7 +20,7 @@
1134 <script type="text/javascript">
1135 registerLaunchpadFunction(insertAllExpansionButtons);
1136
1137- YUI().use('node', 'cookie', 'anim', 'lp.pofile', function(Y) {
1138+ LPS.use('node', 'cookie', 'anim', 'lp.pofile', function(Y) {
1139
1140 var hide_notification = function(node) {
1141 var hide_anim = new Y.Anim({
1142
1143=== modified file 'lib/lp/translations/templates/translation-import-queue-macros.pt'
1144--- lib/lp/translations/templates/translation-import-queue-macros.pt 2009-11-20 14:15:34 +0000
1145+++ lib/lp/translations/templates/translation-import-queue-macros.pt 2009-12-07 21:11:12 +0000
1146@@ -18,7 +18,7 @@
1147 </script>
1148
1149 <script type="text/javascript">
1150- YUI().use( 'translations', 'event', function(Y) {
1151+ LPS.use( 'translations', 'event', function(Y) {
1152 Y.on('domready', function(e) {
1153 Y.translations.initialize_import_queue_page(Y);
1154 });
1155
1156=== modified file 'lib/lp/translations/templates/translationimportqueueentry-index.pt'
1157--- lib/lp/translations/templates/translationimportqueueentry-index.pt 2009-11-04 13:56:17 +0000
1158+++ lib/lp/translations/templates/translationimportqueueentry-index.pt 2009-12-07 21:11:12 +0000
1159@@ -14,7 +14,7 @@
1160 }
1161 </style>
1162 <script type="text/javascript">
1163- YUI().use('node', 'lazr.anim', function(Y) {
1164+ LPS.use('node', 'lazr.anim', function(Y) {
1165 var fields = {'POT':
1166 ['field.name', 'field.translation_domain',
1167 'field.languagepack'],
1168
1169=== modified file 'lib/lp/translations/templates/translationmessage-translate.pt'
1170--- lib/lp/translations/templates/translationmessage-translate.pt 2009-09-17 07:28:30 +0000
1171+++ lib/lp/translations/templates/translationmessage-translate.pt 2009-12-07 21:11:12 +0000
1172@@ -18,7 +18,7 @@
1173 tal:define="lp_js string:${icingroot}/build"
1174 tal:attributes="src string:${lp_js}/translations/pofile.js"></script>
1175 <script type="text/javascript">
1176- YUI().use('node', 'lp.pofile', function(Y) {
1177+ LPS.use('node', 'lp.pofile', function(Y) {
1178 Y.on('domready', Y.lp.pofile.setupSuggestionDismissal);
1179 });
1180 </script>

Subscribers

People subscribed via source and target branches

to status/vote changes: