Merge lp:~edwin-grubbs/launchpad/bug-297833-invite-private-team into lp:launchpad/db-devel
- bug-297833-invite-private-team
- Merge into db-devel
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 |
Related bugs: |
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.
Description of the change
Edwin Grubbs (edwin-grubbs) wrote : | # |
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.
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.
Edwin Grubbs (edwin-grubbs) wrote : | # |
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 ParticipatingPe
* "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 ParticipatingPe
* "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/
--- lib/canonical/
+++ lib/canonical/
@@ -37,6 +37,8 @@
'PasswordF
'PillarAli
'PillarNam
+ 'PrivateMembers
+ 'PrivateTeamNot
'ProductBu
'ProductNa
'PublicPer
@@ -758,7 +760,7 @@
"""Return True if the person is public."""
from canonical.
if not IPerson.
- raise ConstraintNotSa
+ return False
if person.visibility == PersonVisibilit
return True
else:
@@ -770,7 +772,7 @@
"""True if the person/team has private membership visibility."""
from canonical.
Curtis Hovey (sinzui) wrote : | # |
Thanks Edwin.
This s a great re-implementation. This is good to land when PQM opens.
Preview Diff
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:<script id='milestone-script' type='text/javascript'>" /> |
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:<script id='branchlink-script' type='text/javascript'>" /> |
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:<script id='milestone-script' type='text/javascript'>" /> |
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> |
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 /launchpad. dev/~guadamen/ +add-my- teams
* Add a new team with the visibility set to private.
* Open https:/
* The newly created team should not appear in this list.