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
=== 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-07 21:11:12 +0000
@@ -37,6 +37,8 @@
37 'PasswordField',37 'PasswordField',
38 'PillarAliases',38 'PillarAliases',
39 'PillarNameField',39 'PillarNameField',
40 'PrivateMembershipTeamNotAllowed',
41 'PrivateTeamNotAllowed',
40 'ProductBugTracker',42 'ProductBugTracker',
41 'ProductNameField',43 'ProductNameField',
42 'PublicPersonChoice',44 'PublicPersonChoice',
@@ -758,7 +760,7 @@
758 """Return True if the person is public."""760 """Return True if the person is public."""
759 from canonical.launchpad.interfaces import IPerson, PersonVisibility761 from canonical.launchpad.interfaces import IPerson, PersonVisibility
760 if not IPerson.providedBy(person):762 if not IPerson.providedBy(person):
761 raise ConstraintNotSatisfied("Expected a person.")763 return False
762 if person.visibility == PersonVisibility.PUBLIC:764 if person.visibility == PersonVisibility.PUBLIC:
763 return True765 return True
764 else:766 else:
@@ -770,7 +772,7 @@
770 """True if the person/team has private membership visibility."""772 """True if the person/team has private membership visibility."""
771 from canonical.launchpad.interfaces import IPerson, PersonVisibility773 from canonical.launchpad.interfaces import IPerson, PersonVisibility
772 if not IPerson.providedBy(person):774 if not IPerson.providedBy(person):
773 raise ConstraintNotSatisfied("Expected a person.")775 return False
774 if person.visibility == PersonVisibility.PRIVATE_MEMBERSHIP:776 if person.visibility == PersonVisibility.PRIVATE_MEMBERSHIP:
775 # PRIVATE_MEMBERSHIP.777 # PRIVATE_MEMBERSHIP.
776 return True778 return True
@@ -779,16 +781,36 @@
779 return False781 return False
780782
781783
782class PublicPersonChoice(Choice):784class PrivateTeamNotAllowed(ConstraintNotSatisfied):
785 __doc__ = _("A private team is not allowed.")
786
787
788class PrivateMembershipTeamNotAllowed(ConstraintNotSatisfied):
789 __doc__ = _("A private-membership team is not allowed.")
790
791
792class PersonChoice(Choice):
793 """A person or team.
794
795 This is useful as a superclass and provides a clearer error message than
796 "Constraint not satisfied".
797 """
798 implements(IReferenceChoice)
799 schema = IObject # Will be set to IPerson once IPerson is defined.
800
801
802class PublicPersonChoice(PersonChoice):
783 """A person or team who is public."""803 """A person or team who is public."""
784 implements(IReferenceChoice)
785 schema = IObject # Will be set to IPerson once IPerson is defined.
786804
787 def constraint(self, value):805 def constraint(self, value):
788 return is_valid_public_person(value)806 if is_valid_public_person(value):
789807 return True
790808 else:
791class ParticipatingPersonChoice(Choice):809 # The vocabulary prevents the revealing of private team names.
810 raise PrivateTeamNotAllowed(value)
811
812
813class ParticipatingPersonChoice(PersonChoice):
792 """A person or team who is not a private membership team.814 """A person or team who is not a private membership team.
793815
794 A person can participate in all contexts. A PRIVATE team can participate816 A person can participate in all contexts. A PRIVATE team can participate
@@ -796,8 +818,10 @@
796 user. A PRIVATE MEMBERSHIP team is severely limited in the roles in which818 user. A PRIVATE MEMBERSHIP team is severely limited in the roles in which
797 it can participate.819 it can participate.
798 """820 """
799 implements(IReferenceChoice)
800 schema = IObject # Will be set to IPerson once IPerson is defined.
801821
802 def constraint(self, value):822 def constraint(self, value):
803 return not is_private_membership(value)823 if not is_private_membership(value):
824 return True
825 else:
826 # The vocabulary prevents the revealing of private team names.
827 raise PrivateMembershipTeamNotAllowed(value)
804828
=== modified file 'lib/lp/archiveuploader/nascentuploadfile.py'
--- lib/lp/archiveuploader/nascentuploadfile.py 2009-11-10 13:09:26 +0000
+++ lib/lp/archiveuploader/nascentuploadfile.py 2009-12-07 21:11:12 +0000
@@ -690,7 +690,7 @@
690 tar_checker.ancient_files[first_file])690 tar_checker.ancient_files[first_file])
691 yield UploadError(691 yield UploadError(
692 "%s: has %s file(s) with a time stamp too "692 "%s: has %s file(s) with a time stamp too "
693 "far into the future (e.g. %s [%s])."693 "far in the past (e.g. %s [%s])."
694 % (self.filename, len(ancient_files), first_file,694 % (self.filename, len(ancient_files), first_file,
695 timestamp))695 timestamp))
696 return696 return
697697
=== modified file 'lib/lp/archiveuploader/tests/nascentuploadfile.txt'
--- lib/lp/archiveuploader/tests/nascentuploadfile.txt 2009-07-08 08:38:05 +0000
+++ lib/lp/archiveuploader/tests/nascentuploadfile.txt 2009-12-07 21:11:12 +0000
@@ -539,6 +539,77 @@
539539
540== DebBinaryUploadFile ==540== DebBinaryUploadFile ==
541541
542DebBinaryUploadFile models a binary .deb file.
543
544 >>> from lp.archiveuploader.nascentuploadfile import (
545 ... DebBinaryUploadFile)
546 >>> ed_deb_path = datadir('ed_0.2-20_i386.deb')
547 >>> ed_binary_deb = DebBinaryUploadFile(ed_deb_path,
548 ... 'e31eeb0b6b3b87e1ea79378df864ffff',
549 ... 15, 'main/editors', 'important', 'foo', '1.2',
550 ... ed_mixed_changes, modified_insecure_policy,
551 ... mock_logger_quiet)
552
553Like the other files it can be verified:
554
555 >>> list(ed_binary_deb.verify())
556 []
557
558Verification checks that the specified section matches the section in the
559changes file:
560
561 >>> ed_binary_deb = DebBinaryUploadFile(ed_deb_path,
562 ... 'e31eeb0b6b3b87e1ea79378df864ffff',
563 ... 15, 'main/net', 'important', 'foo', '1.2',
564 ... ed_mixed_changes, modified_insecure_policy,
565 ... mock_logger_quiet)
566 >>> list(ed_binary_deb.verify())
567 [UploadError('ed_0.2-20_i386.deb control file lists section as
568 main/editors but changes file has main/net.',)]
569
570It also checks the priority against the changes file:
571
572 >>> ed_binary_deb = DebBinaryUploadFile(ed_deb_path,
573 ... 'e31eeb0b6b3b87e1ea79378df864ffff',
574 ... 15, 'main/editors', 'extra', 'foo', '1.2',
575 ... ed_mixed_changes, modified_insecure_policy,
576 ... mock_logger_quiet)
577 >>> list(ed_binary_deb.verify())
578 [UploadError('ed_0.2-20_i386.deb control file lists priority as important
579 but changes file has extra.',)]
580
581The timestamp of the files in the .deb are tested against the policy for being
582too new:
583
584 >>> old_only_policy = getPolicy(
585 ... name='insecure', distro='ubuntu', distroseries='hoary')
586 >>> old_only_policy.can_upload_binaries = True
587 >>> old_only_policy.future_time_grace = -5 * 365 * 24 * 60 * 60
588
589 >>> ed_binary_deb = DebBinaryUploadFile(ed_deb_path,
590 ... 'e31eeb0b6b3b87e1ea79378df864ffff',
591 ... 15, 'main/editors', 'important', 'foo', '1.2',
592 ... ed_mixed_changes, old_only_policy,
593 ... mock_logger_quiet)
594 >>> list(ed_binary_deb.verifyDebTimestamp())
595 [UploadError('ed_0.2-20_i386.deb: has 26 file(s) with a time stamp too
596 far into the future (e.g. control [Thu Jan 3 19:29:01 2008]).',)]
597
598... as well as for being too old:
599
600 >>> new_only_policy = getPolicy(
601 ... name='insecure', distro='ubuntu', distroseries='hoary')
602 >>> new_only_policy.can_upload_binaries = True
603 >>> new_only_policy.earliest_year = 2010
604 >>> ed_binary_deb = DebBinaryUploadFile(ed_deb_path,
605 ... 'e31eeb0b6b3b87e1ea79378df864ffff',
606 ... 15, 'main/editors', 'important', 'foo', '1.2',
607 ... ed_mixed_changes, new_only_policy,
608 ... mock_logger_quiet)
609 >>> list(ed_binary_deb.verify())
610 [UploadError('ed_0.2-20_i386.deb: has 26 file(s) with a time stamp too
611 far in the past (e.g. control [Thu Jan 3 19:29:01 2008]).',)]
612
542613
543== UDebBinaryUploadFile ==614== UDebBinaryUploadFile ==
544615
545616
=== modified file 'lib/lp/bugs/templates/bug-portlet-subscribers.pt'
--- lib/lp/bugs/templates/bug-portlet-subscribers.pt 2009-11-26 03:13:32 +0000
+++ lib/lp/bugs/templates/bug-portlet-subscribers.pt 2009-12-07 21:11:12 +0000
@@ -25,7 +25,7 @@
25 <img src="/@@/spinner" />25 <img src="/@@/spinner" />
26 </div>26 </div>
27 <script type="text/javascript">27 <script type="text/javascript">
28 YUI().use('io-base', 'node', 'bugs.bugtask_index', function(Y) {28 LPS.use('io-base', 'node', 'bugs.bugtask_index', function(Y) {
29 // Must be done inline here to ensure the load event fires.29 // Must be done inline here to ensure the load event fires.
30 // This is a work around for a YUI3 issue with event handling.30 // This is a work around for a YUI3 issue with event handling.
31 var subscription_link = Y.one('.menu-link-subscription');31 var subscription_link = Y.one('.menu-link-subscription');
3232
=== modified file 'lib/lp/bugs/templates/bugtarget-filebug-submit-bug.pt'
--- lib/lp/bugs/templates/bugtarget-filebug-submit-bug.pt 2009-10-01 12:09:37 +0000
+++ lib/lp/bugs/templates/bugtarget-filebug-submit-bug.pt 2009-12-07 21:11:12 +0000
@@ -14,7 +14,7 @@
14 tal:define="lp_js string:${icingroot}/build"14 tal:define="lp_js string:${icingroot}/build"
15 tal:attributes="src string:${lp_js}/bugs/filebug-dupefinder.js"></script>15 tal:attributes="src string:${lp_js}/bugs/filebug-dupefinder.js"></script>
16 <script type="text/javascript">16 <script type="text/javascript">
17 YUI().use('base', 'node', 'oop', 'event', 'bugs.dupe_finder', function(Y) {17 LPS.use('base', 'node', 'oop', 'event', 'bugs.dupe_finder', function(Y) {
18 Y.bugs.setup_dupe_finder();18 Y.bugs.setup_dupe_finder();
19 });19 });
20 </script>20 </script>
2121
=== modified file 'lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt'
--- lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt 2009-11-04 13:56:17 +0000
+++ lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt 2009-12-07 21:11:12 +0000
@@ -12,7 +12,7 @@
12 <img src="/@@/spinner" />12 <img src="/@@/spinner" />
13 </div>13 </div>
14 <script type="text/javascript">14 <script type="text/javascript">
15 YUI().use('io-base', 'node', function(Y) {15 LPS.use('io-base', 'node', function(Y) {
16 Y.on('domready', function() {16 Y.on('domready', function() {
17 var portlet = Y.one('#portlet-bugfilters');17 var portlet = Y.one('#portlet-bugfilters');
18 Y.one('#bugfilters-portlet-spinner').setStyle('display', 'block');18 Y.one('#bugfilters-portlet-spinner').setStyle('display', 'block');
1919
=== modified file 'lib/lp/bugs/templates/bugtarget-portlet-bugtags.pt'
--- lib/lp/bugs/templates/bugtarget-portlet-bugtags.pt 2009-11-04 13:56:17 +0000
+++ lib/lp/bugs/templates/bugtarget-portlet-bugtags.pt 2009-12-07 21:11:12 +0000
@@ -9,7 +9,7 @@
9 <a id="tags-content-link"9 <a id="tags-content-link"
10 tal:attributes="href context/fmt:url/+bugtarget-portlet-tags-content"></a>10 tal:attributes="href context/fmt:url/+bugtarget-portlet-tags-content"></a>
11 <script type="text/javascript">11 <script type="text/javascript">
12 YUI().use('io-base', 'node', function(Y) {12 LPS.use('io-base', 'node', function(Y) {
13 Y.on('domready', function() {13 Y.on('domready', function() {
14 Y.one('#tags-portlet-spinner').setStyle('display', 'block');14 Y.one('#tags-portlet-spinner').setStyle('display', 'block');
1515
1616
=== modified file 'lib/lp/bugs/templates/bugtask-index.pt'
--- lib/lp/bugs/templates/bugtask-index.pt 2009-11-30 17:57:15 +0000
+++ lib/lp/bugs/templates/bugtask-index.pt 2009-12-07 21:11:12 +0000
@@ -37,7 +37,7 @@
37 </script>37 </script>
38 </tal:devmode>38 </tal:devmode>
39 <script type="text/javascript">39 <script type="text/javascript">
40 YUI().use('base', 'node', 'oop', 'event', 'bugs.bugtask_index',40 LPS.use('base', 'node', 'oop', 'event', 'bugs.bugtask_index',
41 'code.branchmergeproposal.popupdiff', function(Y) {41 'code.branchmergeproposal.popupdiff', function(Y) {
42 Y.bugs.setup_bugtask_index();42 Y.bugs.setup_bugtask_index();
43 Y.on('load', function(e) {43 Y.on('load', function(e) {
@@ -155,7 +155,7 @@
155 <img src="/@@/spinner" id="tags-edit-spinner" style="display: none" />155 <img src="/@@/spinner" id="tags-edit-spinner" style="display: none" />
156 <a href="+edit" title="Edit tags" id="edit-tags-trigger" class="sprite edit"></a>156 <a href="+edit" title="Edit tags" id="edit-tags-trigger" class="sprite edit"></a>
157 <script type="text/javascript">157 <script type="text/javascript">
158 YUI().use('event', 'node', 'bugs.bug_tags_entry', function(Y) {158 LPS.use('event', 'node', 'bugs.bug_tags_entry', function(Y) {
159 // XXX intellectronica 2009-04-16 bug #362309:159 // XXX intellectronica 2009-04-16 bug #362309:
160 // The load event fires very late on bug pages that take a160 // The load event fires very late on bug pages that take a
161 // long time to render, but we prefer to use it since the161 // long time to render, but we prefer to use it since the
@@ -295,7 +295,7 @@
295 button.style.display = 'none';295 button.style.display = 'none';
296 </script>296 </script>
297 <script type="text/javascript">297 <script type="text/javascript">
298 YUI().use('lp.comment', function(Y) {298 LPS.use('lp.comment', function(Y) {
299 var comment = new Y.lp.Comment();299 var comment = new Y.lp.Comment();
300 comment.render();300 comment.render();
301 });301 });
302302
=== modified file 'lib/lp/bugs/templates/bugtask-tasks-and-nominations-table-row.pt'
--- lib/lp/bugs/templates/bugtask-tasks-and-nominations-table-row.pt 2009-11-03 15:32:31 +0000
+++ lib/lp/bugs/templates/bugtask-tasks-and-nominations-table-row.pt 2009-12-07 21:11:12 +0000
@@ -185,7 +185,7 @@
185 class="bugtasks-table-row-init-script"185 class="bugtasks-table-row-init-script"
186 tal:condition="not:view/many_bugtasks"186 tal:condition="not:view/many_bugtasks"
187 tal:content="string:187 tal:content="string:
188 YUI().use('event', 'bugs.bugtask_index', function(Y) {188 LPS.use('event', 'bugs.bugtask_index', function(Y) {
189 Y.on('load',189 Y.on('load',
190 function(e) {190 function(e) {
191 Y.bugs.setup_bugtask_row(${view/js_config});191 Y.bugs.setup_bugtask_row(${view/js_config});
192192
=== modified file 'lib/lp/bugs/templates/bugtasks-and-nominations-table.pt'
--- lib/lp/bugs/templates/bugtasks-and-nominations-table.pt 2009-09-02 22:13:06 +0000
+++ lib/lp/bugs/templates/bugtasks-and-nominations-table.pt 2009-12-07 21:11:12 +0000
@@ -88,7 +88,7 @@
88 </span>88 </span>
8989
90 <script type="text/javascript" tal:content="string:90 <script type="text/javascript" tal:content="string:
91 YUI().use('event', 'bugs.bugtask_index', function(Y) {91 LPS.use('event', 'bugs.bugtask_index', function(Y) {
92 Y.on('load', function(e) {92 Y.on('load', function(e) {
93 Y.bugs.setup_me_too(${view/current_user_affected_js_status});93 Y.bugs.setup_me_too(${view/current_user_affected_js_status});
94 }, window);94 }, window);
9595
=== modified file 'lib/lp/bugs/templates/official-bug-target-manage-tags.pt'
--- lib/lp/bugs/templates/official-bug-target-manage-tags.pt 2009-09-04 17:03:00 +0000
+++ lib/lp/bugs/templates/official-bug-target-manage-tags.pt 2009-12-07 21:11:12 +0000
@@ -31,7 +31,7 @@
31 </script>31 </script>
32 <script tal:replace="structure view/tags_js_data" />32 <script tal:replace="structure view/tags_js_data" />
33 <script type="text/javascript">33 <script type="text/javascript">
34 YUI().use('event', 'bugs.official_bug_tag_management', function(Y) {34 LPS.use('event', 'bugs.official_bug_tag_management', function(Y) {
35 Y.on('domready', function(e) {35 Y.on('domready', function(e) {
36 Y.bugs.setup_official_bug_tag_management();36 Y.bugs.setup_official_bug_tag_management();
37 });37 });
3838
=== modified file 'lib/lp/code/templates/branch-import-details.pt'
--- lib/lp/code/templates/branch-import-details.pt 2009-11-04 13:56:17 +0000
+++ lib/lp/code/templates/branch-import-details.pt 2009-12-07 21:11:12 +0000
@@ -32,7 +32,7 @@
32 Try again32 Try again
33 </a>33 </a>
34 <script type="text/javascript">34 <script type="text/javascript">
35 YUI().use('event', 'node', function(Y) {35 LPS.use('event', 'node', function(Y) {
36 Y.on("domready", function () { Y.one('#tryagainlink').setStyle('display', 'inline') });36 Y.on("domready", function () { Y.one('#tryagainlink').setStyle('display', 'inline') });
37 });37 });
38 </script>38 </script>
3939
=== modified file 'lib/lp/code/templates/branch-index.pt'
--- lib/lp/code/templates/branch-index.pt 2009-11-17 05:07:41 +0000
+++ lib/lp/code/templates/branch-index.pt 2009-12-07 21:11:12 +0000
@@ -47,7 +47,7 @@
47 </tal:devmode>47 </tal:devmode>
48 <script type="text/javascript"48 <script type="text/javascript"
49 tal:content="string:49 tal:content="string:
50 YUI().use('node', 'event', 'widget', 'plugin', 'overlay',50 LPS.use('node', 'event', 'widget', 'plugin', 'overlay',
51 'lazr.choiceedit', 'code.branchstatus',51 'lazr.choiceedit', 'code.branchstatus',
52 'code.branchmergeproposal.popupdiff',52 'code.branchmergeproposal.popupdiff',
53 function(Y) {53 function(Y) {
5454
=== modified file 'lib/lp/code/templates/branch-listing.pt'
--- lib/lp/code/templates/branch-listing.pt 2009-11-04 13:56:17 +0000
+++ lib/lp/code/templates/branch-listing.pt 2009-12-07 21:11:12 +0000
@@ -41,7 +41,7 @@
41}41}
42registerLaunchpadFunction(hookUpFilterSubmission);42registerLaunchpadFunction(hookUpFilterSubmission);
4343
44YUI().use('io-base', 'node', 'json-parse', function(Y) {44LPS.use('io-base', 'node', 'json-parse', function(Y) {
4545
46function doUpdate(transaction_id, response, args) {46function doUpdate(transaction_id, response, args) {
47 json_values = Y.JSON.parse(response.responseText);47 json_values = Y.JSON.parse(response.responseText);
4848
=== modified file 'lib/lp/code/templates/branch-portlet-subscribers.pt'
--- lib/lp/code/templates/branch-portlet-subscribers.pt 2009-11-04 13:56:17 +0000
+++ lib/lp/code/templates/branch-portlet-subscribers.pt 2009-12-07 21:11:12 +0000
@@ -41,7 +41,7 @@
41 string:&lt;script id='milestone-script' type='text/javascript'&gt;" />41 string:&lt;script id='milestone-script' type='text/javascript'&gt;" />
42 <!--42 <!--
4343
44 YUI().use('io-base', 'node', 'code.branchsubscription', function(Y) {44 LPS.use('io-base', 'node', 'code.branchsubscription', function(Y) {
4545
46 if(Y.UA.ie) {46 if(Y.UA.ie) {
47 Y.one('#subscriber-list').set('innerHTML',47 Y.one('#subscriber-list').set('innerHTML',
4848
=== modified file 'lib/lp/code/templates/branch-related-bugs-specs.pt'
--- lib/lp/code/templates/branch-related-bugs-specs.pt 2009-09-08 21:42:45 +0000
+++ lib/lp/code/templates/branch-related-bugs-specs.pt 2009-12-07 21:11:12 +0000
@@ -42,7 +42,7 @@
42 string:&lt;script id='branchlink-script' type='text/javascript'&gt;" />42 string:&lt;script id='branchlink-script' type='text/javascript'&gt;" />
43 <!--43 <!--
4444
45 YUI().use('io-base', 'code.branchlinks', function(Y) {45 LPS.use('io-base', 'code.branchlinks', function(Y) {
4646
47 if(Y.UA.ie) {47 if(Y.UA.ie) {
48 return;48 return;
4949
=== modified file 'lib/lp/code/templates/branchmergeproposal-generic-listing.pt'
--- lib/lp/code/templates/branchmergeproposal-generic-listing.pt 2009-11-04 13:56:17 +0000
+++ lib/lp/code/templates/branchmergeproposal-generic-listing.pt 2009-12-07 21:11:12 +0000
@@ -24,7 +24,7 @@
24 </form>24 </form>
25<script type="text/javascript">25<script type="text/javascript">
2626
27YUI().use('node', function(Y) {27LPS.use('node', function(Y) {
2828
29 function submit_filter() {29 function submit_filter() {
30 Y.one('#filter_form').submit();30 Y.one('#filter_form').submit();
3131
=== modified file 'lib/lp/registry/browser/person.py'
--- lib/lp/registry/browser/person.py 2009-11-21 14:45:26 +0000
+++ lib/lp/registry/browser/person.py 2009-12-07 21:11:12 +0000
@@ -4114,9 +4114,13 @@
4114 for team in self.user.getAdministratedTeams():4114 for team in self.user.getAdministratedTeams():
4115 if team == self.context:4115 if team == self.context:
4116 continue4116 continue
4117 elif team.visibility != PersonVisibility.PUBLIC:
4118 continue
4117 elif team in self.context.activemembers:4119 elif team in self.context.activemembers:
4120 # The team is already a member of the context object.
4118 continue4121 continue
4119 elif self.context.hasParticipationEntryFor(team):4122 elif self.context.hasParticipationEntryFor(team):
4123 # The context object is a member/submember of the team.
4120 continue4124 continue
4121 candidates.append(team)4125 candidates.append(team)
4122 return candidates4126 return candidates
41234127
=== modified file 'lib/lp/registry/browser/tests/productrelease-views.txt'
--- lib/lp/registry/browser/tests/productrelease-views.txt 2009-09-11 16:00:24 +0000
+++ lib/lp/registry/browser/tests/productrelease-views.txt 2009-12-07 21:11:12 +0000
@@ -130,7 +130,7 @@
130 >>> script = find_tag_by_id(view.render(), 'milestone-script')130 >>> script = find_tag_by_id(view.render(), 'milestone-script')
131 >>> print script131 >>> print script
132 <script id="milestone-script" type="text/javascript">132 <script id="milestone-script" type="text/javascript">
133 YUI().use(... 'lp.milestoneoverlay'...133 LPS.use(... 'lp.milestoneoverlay'...
134 var milestone_form_uri = '.../app/simple/+addmilestone/++form++';134 var milestone_form_uri = '.../app/simple/+addmilestone/++form++';
135 var series_uri = '/app/simple';135 var series_uri = '/app/simple';
136 ...136 ...
137137
=== modified file 'lib/lp/registry/browser/tests/team-views.txt'
--- lib/lp/registry/browser/tests/team-views.txt 2009-10-26 21:12:49 +0000
+++ lib/lp/registry/browser/tests/team-views.txt 2009-12-07 21:11:12 +0000
@@ -274,6 +274,7 @@
274 >>> from lp.registry.interfaces.person import PersonVisibility274 >>> from lp.registry.interfaces.person import PersonVisibility
275 >>> login('admin@canonical.com')275 >>> login('admin@canonical.com')
276 >>> private_team = factory.makeTeam(276 >>> private_team = factory.makeTeam(
277 ... owner=sample_person,
277 ... name='private-team', displayname='Private Team',278 ... name='private-team', displayname='Private Team',
278 ... visibility=PersonVisibility.PRIVATE)279 ... visibility=PersonVisibility.PRIVATE)
279280
@@ -286,6 +287,7 @@
286Private membership teams are also indicated as private.287Private membership teams are also indicated as private.
287288
288 >>> private_membership_team = factory.makeTeam(289 >>> private_membership_team = factory.makeTeam(
290 ... owner=sample_person,
289 ... name='private-membership-team',291 ... name='private-membership-team',
290 ... displayname='Private Membership Team',292 ... displayname='Private Membership Team',
291 ... visibility=PersonVisibility.PRIVATE_MEMBERSHIP)293 ... visibility=PersonVisibility.PRIVATE_MEMBERSHIP)
@@ -294,3 +296,28 @@
294 Team membership is viewable by team members296 Team membership is viewable by team members
295 >>> print view.visibility_portlet_class297 >>> print view.visibility_portlet_class
296 portlet private298 portlet private
299
300== +add-my-teams ==
301
302This page lists the teams that you administer and can add as a member
303to the current team. Private teams cannot be added as members to another
304team.
305
306 >>> login_person(sample_person)
307 >>> view = create_initialized_view(guadamen, '+add-my-teams')
308 >>> for candidate in view.candidate_teams:
309 ... print candidate.name, candidate.visibility.title
310 landscape-developers Public
311 launchpad-users Public
312
313Making one of the teams that sample person owns public will cause it
314to show up.
315
316 >>> removeSecurityProxy(private_membership_team).visibility = (
317 ... PersonVisibility.PUBLIC)
318 >>> view = create_initialized_view(guadamen, '+add-my-teams')
319 >>> for candidate in view.candidate_teams:
320 ... print candidate.name, candidate.visibility.title
321 landscape-developers Public
322 launchpad-users Public
323 private-membership-team Public
297324
=== modified file 'lib/lp/registry/interfaces/person.py'
--- lib/lp/registry/interfaces/person.py 2009-12-05 13:17:53 +0000
+++ lib/lp/registry/interfaces/person.py 2009-12-07 21:11:12 +0000
@@ -65,10 +65,9 @@
6565
66from canonical.database.sqlbase import block_implicit_flushes66from canonical.database.sqlbase import block_implicit_flushes
67from canonical.launchpad.fields import (67from canonical.launchpad.fields import (
68 BlacklistableContentNameField, IconImageUpload, LogoImageUpload,68 BlacklistableContentNameField, IconImageUpload, is_private_membership,
69 MugshotImageUpload, ParticipatingPersonChoice, PasswordField,69 is_valid_public_person, LogoImageUpload, MugshotImageUpload,
70 PublicPersonChoice, StrippedTextLine, is_private_membership,70 PasswordField, PersonChoice, PublicPersonChoice, StrippedTextLine)
71 is_valid_public_person)
72from canonical.launchpad.interfaces.account import AccountStatus, IAccount71from canonical.launchpad.interfaces.account import AccountStatus, IAccount
73from canonical.launchpad.interfaces.emailaddress import IEmailAddress72from canonical.launchpad.interfaces.emailaddress import IEmailAddress
74from lp.app.interfaces.headings import IRootContext73from lp.app.interfaces.headings import IRootContext
@@ -1518,8 +1517,7 @@
15181517
1519# Set the schemas to the newly defined interface for classes that deferred1518# Set the schemas to the newly defined interface for classes that deferred
1520# doing so when defined.1519# doing so when defined.
1521PublicPersonChoice.schema = IPerson1520PersonChoice.schema = IPerson
1522ParticipatingPersonChoice.schema = IPerson
15231521
15241522
1525class INewPersonForm(IPerson):1523class INewPersonForm(IPerson):
15261524
=== modified file 'lib/lp/registry/stories/teammembership/xx-private-membership.txt'
--- lib/lp/registry/stories/teammembership/xx-private-membership.txt 2009-10-26 21:12:49 +0000
+++ lib/lp/registry/stories/teammembership/xx-private-membership.txt 2009-12-07 21:11:12 +0000
@@ -234,7 +234,8 @@
234 >>> admin_browser.url234 >>> admin_browser.url
235 'http://launchpad.dev/%7Esimple-team/+addmember'235 'http://launchpad.dev/%7Esimple-team/+addmember'
236 >>> get_feedback_messages(admin_browser.contents)236 >>> get_feedback_messages(admin_browser.contents)
237 [...Constraint not satisfied...237 [...There is 1 error...
238 ...A private team is not allowed.']
238239
239Anonymous users cannot even know that the private membership team even240Anonymous users cannot even know that the private membership team even
240exists.241exists.
@@ -305,7 +306,7 @@
305 >>> admin_browser.url306 >>> admin_browser.url
306 'http://bugs.launchpad.dev/firefox/+bug/1/+addsubscriber'307 'http://bugs.launchpad.dev/firefox/+bug/1/+addsubscriber'
307 >>> get_feedback_messages(admin_browser.contents)308 >>> get_feedback_messages(admin_browser.contents)
308 [...Constraint not satisfied...309 [...A private-membership team is not allowed...
309310
310311
311== Restrict Subscribing to Blueprints ==312== Restrict Subscribing to Blueprints ==
@@ -320,7 +321,7 @@
320 >>> admin_browser.url321 >>> admin_browser.url
321 'http://blueprints.launchpad.dev/firefox/+spec/canvas/+addsubscriber'322 'http://blueprints.launchpad.dev/firefox/+spec/canvas/+addsubscriber'
322 >>> get_feedback_messages(admin_browser.contents)323 >>> get_feedback_messages(admin_browser.contents)
323 [...Constraint not satisfied...324 [...A private team is not allowed...
324325
325326
326== Restrict Appointing a Translator ==327== Restrict Appointing a Translator ==
@@ -336,7 +337,7 @@
336 >>> admin_browser.url337 >>> admin_browser.url
337 'http://translations.launchpad.dev/+groups/testing-translation-team/+appoint'338 'http://translations.launchpad.dev/+groups/testing-translation-team/+appoint'
338 >>> get_feedback_messages(admin_browser.contents)339 >>> get_feedback_messages(admin_browser.contents)
339 [...Constraint not satisfied...340 [...A private team is not allowed...
340341
341342
342== Restrict Project Owner ==343== Restrict Project Owner ==
@@ -365,7 +366,7 @@
365 >>> admin_browser.url366 >>> admin_browser.url
366 'http://launchpad.dev/jokosher/+edit-people'367 'http://launchpad.dev/jokosher/+edit-people'
367 >>> get_feedback_messages(admin_browser.contents)368 >>> get_feedback_messages(admin_browser.contents)
368 [u'There is 1 error.', u'Constraint not satisfied']369 [...A private-membership team is not allowed...
369370
370371
371== Restrict Product Bug Supervisor ==372== Restrict Product Bug Supervisor ==
372373
=== modified file 'lib/lp/registry/templates/object-timeline-graph.pt'
--- lib/lp/registry/templates/object-timeline-graph.pt 2009-11-24 09:30:01 +0000
+++ lib/lp/registry/templates/object-timeline-graph.pt 2009-12-07 21:11:12 +0000
@@ -32,7 +32,7 @@
32 include_inactive = false;32 include_inactive = false;
33 }33 }
3434
35 YUI().use('registry.timeline', 'node', function(Y) {35 LPS.use('registry.timeline', 'node', function(Y) {
36 Y.on('domready', function(e) {36 Y.on('domready', function(e) {
37 if (Y.UA.ie) {37 if (Y.UA.ie) {
38 return;38 return;
3939
=== modified file 'lib/lp/registry/templates/person-macros.pt'
--- lib/lp/registry/templates/person-macros.pt 2009-11-04 13:56:17 +0000
+++ lib/lp/registry/templates/person-macros.pt 2009-12-07 21:11:12 +0000
@@ -190,7 +190,7 @@
190 condition="private_prefix">190 condition="private_prefix">
191 <script type="text/javascript"191 <script type="text/javascript"
192 tal:content="string:192 tal:content="string:
193 YUI().use('node', 'event', function(Y) {193 LPS.use('node', 'event', function(Y) {
194 // Prepend/remove 'private-' from team name based on visibility194 // Prepend/remove 'private-' from team name based on visibility
195 // setting. User can choose to edit it back out, if they wish.195 // setting. User can choose to edit it back out, if they wish.
196 function visibility_on_change(e) {196 function visibility_on_change(e) {
197197
=== modified file 'lib/lp/registry/templates/product-new.pt'
--- lib/lp/registry/templates/product-new.pt 2009-11-04 13:56:17 +0000
+++ lib/lp/registry/templates/product-new.pt 2009-12-07 21:11:12 +0000
@@ -14,7 +14,7 @@
14 * details widgets until the user states that the project they are14 * details widgets until the user states that the project they are
15 * registering is not a duplicate.15 * registering is not a duplicate.
16 */16 */
17YUI().use('node', 'lazr.effects', function(Y) {17LPS.use('node', 'lazr.effects', function(Y) {
18 Y.on('domready', function() {18 Y.on('domready', function() {
19 /* These two regexps serve slightly different purposes. The first19 /* These two regexps serve slightly different purposes. The first
20 * finds the leftmost run of valid url characters for the autofill20 * finds the leftmost run of valid url characters for the autofill
2121
=== modified file 'lib/lp/registry/templates/productrelease-add-from-series.pt'
--- lib/lp/registry/templates/productrelease-add-from-series.pt 2009-11-04 13:56:17 +0000
+++ lib/lp/registry/templates/productrelease-add-from-series.pt 2009-12-07 21:11:12 +0000
@@ -14,7 +14,7 @@
14 <tal:script14 <tal:script
15 replace="structure15 replace="structure
16 string:&lt;script id='milestone-script' type='text/javascript'&gt;" />16 string:&lt;script id='milestone-script' type='text/javascript'&gt;" />
17 YUI().use('node', 'lp.milestoneoverlay', function (Y) {17 LPS.use('node', 'lp.milestoneoverlay', function (Y) {
1818
19 // This is a value for the SELECT OPTION which is passed with19 // This is a value for the SELECT OPTION which is passed with
20 // the SELECT's "change" event. It includes some symbols that are not20 // the SELECT's "change" event. It includes some symbols that are not
2121
=== modified file 'lib/lp/registry/templates/team-add-my-teams.pt'
--- lib/lp/registry/templates/team-add-my-teams.pt 2009-09-02 18:12:57 +0000
+++ lib/lp/registry/templates/team-add-my-teams.pt 2009-12-07 21:11:12 +0000
@@ -8,6 +8,8 @@
8>8>
9<body>9<body>
10 <div metal:fill-slot="main">10 <div metal:fill-slot="main">
11 <p>Private teams cannot be added as a member of another team.</p>
12
11 <p id="no-candidates" tal:condition="not: view/candidate_teams">13 <p id="no-candidates" tal:condition="not: view/candidate_teams">
12 None of the teams you administer can be added to this team.14 None of the teams you administer can be added to this team.
13 </p>15 </p>
1416
=== modified file 'lib/lp/registry/templates/teammembership-index.pt'
--- lib/lp/registry/templates/teammembership-index.pt 2009-11-04 13:56:17 +0000
+++ lib/lp/registry/templates/teammembership-index.pt 2009-12-07 21:11:12 +0000
@@ -20,7 +20,7 @@
20 use-macro="context/@@launchpad_widget_macros/yui2calendar-dependencies" />20 use-macro="context/@@launchpad_widget_macros/yui2calendar-dependencies" />
2121
22 <script type="text/javascript">22 <script type="text/javascript">
23 YUI().use('node', 'lp.calendar', function(Y) {23 LPS.use('node', 'lp.calendar', function(Y) {
24 // Ensure that when the picker is used the radio button switches24 // Ensure that when the picker is used the radio button switches
25 // from 'Never' to 'On' and the expiry field is enabled.25 // from 'Never' to 'On' and the expiry field is enabled.
26 Y.on("available", function(e) {26 Y.on("available", function(e) {
2727
=== modified file 'lib/lp/registry/templates/timeline-macros.pt'
--- lib/lp/registry/templates/timeline-macros.pt 2009-11-04 13:56:17 +0000
+++ lib/lp/registry/templates/timeline-macros.pt 2009-12-07 21:11:12 +0000
@@ -35,7 +35,7 @@
35 if (auto_resize == 'true') {35 if (auto_resize == 'true') {
36 timeline_url += 'resize_frame=timeline-iframe&';36 timeline_url += 'resize_frame=timeline-iframe&';
37 }37 }
38 YUI().use('node', function(Y) {38 LPS.use('node', function(Y) {
39 if (Y.UA.ie) {39 if (Y.UA.ie) {
40 return;40 return;
41 }41 }
4242
=== modified file 'lib/lp/soyuz/templates/archive-edit-dependencies.pt'
--- lib/lp/soyuz/templates/archive-edit-dependencies.pt 2009-11-12 17:26:17 +0000
+++ lib/lp/soyuz/templates/archive-edit-dependencies.pt 2009-12-07 21:11:12 +0000
@@ -62,7 +62,7 @@
62 </div> <!-- launchpad_form -->62 </div> <!-- launchpad_form -->
6363
64<script type="text/javascript">64<script type="text/javascript">
65 YUI().use("node", function(Y) {65 LPS.use("node", function(Y) {
6666
67 // Highlight (setting bold font-weight) the label for the67 // Highlight (setting bold font-weight) the label for the
68 // selected option in a given NodesList. Assumes the input is68 // selected option in a given NodesList. Assumes the input is
6969
=== modified file 'lib/lp/soyuz/templates/archive-macros.pt'
--- lib/lp/soyuz/templates/archive-macros.pt 2009-11-04 19:59:16 +0000
+++ lib/lp/soyuz/templates/archive-macros.pt 2009-12-07 21:11:12 +0000
@@ -10,7 +10,7 @@
10 </tal:comment>10 </tal:comment>
1111
12<script type="text/javascript">12<script type="text/javascript">
13YUI().use('node', 'io-base', 'lazr.anim', 'soyuz-base', function(Y) {13LPS.use('node', 'io-base', 'lazr.anim', 'soyuz-base', function(Y) {
1414
1515
16/*16/*
1717
=== modified file 'lib/lp/soyuz/templates/archive-packages.pt'
--- lib/lp/soyuz/templates/archive-packages.pt 2009-11-04 13:56:17 +0000
+++ lib/lp/soyuz/templates/archive-packages.pt 2009-12-07 21:11:12 +0000
@@ -23,7 +23,7 @@
23 </tal:devmode>23 </tal:devmode>
24 <script type="text/javascript" id="repository-size-update"24 <script type="text/javascript" id="repository-size-update"
25 tal:condition="view/archive_url">25 tal:condition="view/archive_url">
26YUI().use('io-base', 'lazr.anim', 'node', 'soyuz-base',26LPS.use('io-base', 'lazr.anim', 'node', 'soyuz-base',
27 'soyuz.update_archive_build_statuses', function(Y) {27 'soyuz.update_archive_build_statuses', function(Y) {
2828
2929
3030
=== modified file 'lib/lp/soyuz/templates/archive-subscribers.pt'
--- lib/lp/soyuz/templates/archive-subscribers.pt 2009-09-29 07:21:40 +0000
+++ lib/lp/soyuz/templates/archive-subscribers.pt 2009-12-07 21:11:12 +0000
@@ -98,7 +98,7 @@
98 </form>98 </form>
99 </div><!-- class="portlet" -->99 </div><!-- class="portlet" -->
100 <script type="text/javascript" id="setup-archivesubscribers-index">100 <script type="text/javascript" id="setup-archivesubscribers-index">
101 YUI().use('soyuz.archivesubscribers_index', function(Y) {101 LPS.use('soyuz.archivesubscribers_index', function(Y) {
102 Y.soyuz.setup_archivesubscribers_index();102 Y.soyuz.setup_archivesubscribers_index();
103 });103 });
104 </script>104 </script>
105105
=== modified file 'lib/lp/translations/browser/language.py'
--- lib/lp/translations/browser/language.py 2009-10-31 11:06:44 +0000
+++ lib/lp/translations/browser/language.py 2009-12-07 21:11:12 +0000
@@ -29,6 +29,7 @@
29 enabled_with_permission, GetitemNavigation, LaunchpadEditFormView,29 enabled_with_permission, GetitemNavigation, LaunchpadEditFormView,
30 LaunchpadFormView, LaunchpadView, Link, NavigationMenu)30 LaunchpadFormView, LaunchpadView, Link, NavigationMenu)
31from lp.translations.utilities.pluralforms import make_friendly_plural_forms31from lp.translations.utilities.pluralforms import make_friendly_plural_forms
32from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
3233
33from canonical.widgets import LabeledMultiCheckBoxWidget34from canonical.widgets import LabeledMultiCheckBoxWidget
3435
@@ -202,6 +203,13 @@
202203
203 return pluralforms_list204 return pluralforms_list
204205
206 @property
207 def add_question_url(self):
208 rosetta = getUtility(ILaunchpadCelebrities).lp_translations
209 return canonical_url(
210 rosetta,
211 view_name='+addquestion',
212 rootsite='answers')
205213
206class LanguageAdminView(LaunchpadEditFormView):214class LanguageAdminView(LaunchpadEditFormView):
207 """Handle an admin form submission."""215 """Handle an admin form submission."""
208216
=== modified file 'lib/lp/translations/stories/distroseries/xx-distroseries-templates.txt'
--- lib/lp/translations/stories/distroseries/xx-distroseries-templates.txt 2009-10-30 10:09:17 +0000
+++ lib/lp/translations/stories/distroseries/xx-distroseries-templates.txt 2009-12-07 21:11:12 +0000
@@ -1,11 +1,15 @@
1= Templates view for DistroSeries =1
2
3Templates view for DistroSeries
4===============================
25
3The +templates view for DistroSeries gives an overview of the translation6The +templates view for DistroSeries gives an overview of the translation
4templates in this series and provides easy access to the various subpages of7templates in this series and provides easy access to the various subpages of
5each template.8each template.
69
710
8== Getting there ==11Getting there
12-------------
913
10To get to the listing of all templates, one needs to use the link14To get to the listing of all templates, one needs to use the link
11from the distribution series translations page.15from the distribution series translations page.
@@ -16,7 +20,45 @@
16 >>> print user_browser.url20 >>> print user_browser.url
17 http://translations.launchpad.dev/ubuntu/hoary/+templates21 http://translations.launchpad.dev/ubuntu/hoary/+templates
1822
19== The templates table ==23The templates table
24-------------------
25
26Full template listing for a distribution series is reached by following
27a link from the distribution series translations page.
28
29 >>> anon_browser.open(
30 ... 'http://translations.launchpad.dev/ubuntu/hoary')
31 >>> anon_browser.getLink('full list of templates').click()
32
33Full listing of templates shows source package name, template name and
34the date of last update for this distribution series.
35
36 >>> table = find_tag_by_id(anon_browser.contents, 'templates_table')
37 >>> print extract_text(table)
38 Source package Template name Last update
39 evolution disabled-template 2007-01-05
40 evolution evolution-2.2 2005-05-06
41 evolution man 2006-08-14
42 mozilla pkgconf-mozilla 2005-05-06
43 pmount man 2006-08-14
44 pmount pmount 2005-05-06
45
46
47Logged-in users will see a link from distro series
48 >>> user_browser.open(
49 ... 'http://translations.launchpad.dev/ubuntu/hoary')
50 >>> user_browser.getLink('full list of templates').click()
51
52Logged-in users can also choose to download all translations for each
53of the templates.
54
55 >>> table = find_tag_by_id(user_browser.contents, 'templates_table')
56 >>> print extract_text(table)
57 Source package Template name Last update Actions
58 evolution disabled-template 2007-01-05 Download
59 ...
60 mozilla pkgconf-mozilla 2005-05-06 Download
61 ...
2062
21Administrator can see all editing options.63Administrator can see all editing options.
2264
@@ -28,16 +70,17 @@
2870
29 >>> table = find_tag_by_id(admin_browser.contents, 'templates_table')71 >>> table = find_tag_by_id(admin_browser.contents, 'templates_table')
30 >>> print extract_text(table)72 >>> print extract_text(table)
31 Source package Template name Actions73 Source package Template name Last update Actions
32 evolution disabled-template Edit Upload Download Administer74 evolution disabled-template 2007-01-05 Edit Upload Download Administer
33 evolution evolution-2.2 Edit Upload Download Administer75 evolution evolution-2.2 2005-05-06 Edit Upload Download Administer
34 evolution man Edit Upload Download Administer76 evolution man 2006-08-14 Edit Upload Download Administer
35 mozilla pkgconf-mozilla Edit Upload Download Administer77 mozilla pkgconf-mozilla 2005-05-06 Edit Upload Download Administer
36 pmount man Edit Upload Download Administer78 pmount man 2006-08-14 Edit Upload Download Administer
37 pmount pmount Edit Upload Download Administer79 pmount pmount 2005-05-06 Edit Upload Download Administer
3880
3981
40== Links to the templates ==82Links to the templates
83----------------------
4184
42Clicking on a template name will take the user to that template's overview85Clicking on a template name will take the user to that template's overview
43page.86page.
4487
=== modified file 'lib/lp/translations/stories/productseries/xx-productseries-templates.txt'
--- lib/lp/translations/stories/productseries/xx-productseries-templates.txt 2009-10-30 10:09:17 +0000
+++ lib/lp/translations/stories/productseries/xx-productseries-templates.txt 2009-12-07 21:11:12 +0000
@@ -1,13 +1,18 @@
1= Templates view for ProductSeries =1
2
3Templates view for ProductSeries
4================================
25
3The +templates view for ProductSeries gives an overview of the translation6The +templates view for ProductSeries gives an overview of the translation
4templates in this series and provides easy access to the various subpages of7templates in this series and provides easy access to the various subpages of
5each template.8each template.
69
7== Preparation ==10
811Preparation
9To test the ordering of templates in the listing, we need another template12-----------
10that is new but must appear at the top of the list.13
14To test the ordering of templates in the listing, we need another
15template that is new but must appear at the top of the list.
1116
12 >>> login('foo.bar@canonical.com')17 >>> login('foo.bar@canonical.com')
13 >>> from zope.component import getUtility18 >>> from zope.component import getUtility
@@ -18,7 +23,9 @@
18 ... name='at-the-top')23 ... name='at-the-top')
19 >>> logout()24 >>> logout()
2025
21== Getting there ==26
27Getting there
28-------------
2229
23To get to the listing of all templates, one needs to use the link30To get to the listing of all templates, one needs to use the link
24from the product series translations page.31from the product series translations page.
@@ -30,16 +37,17 @@
30 http://translations.launchpad.dev/evolution/trunk/+templates37 http://translations.launchpad.dev/evolution/trunk/+templates
3138
3239
33== The templates table ==40The templates table
41-------------------
3442
35The page shows a table of all templates and links to their subpages.43The page shows a table of all templates and links to their subpages.
3644
37 >>> table = find_tag_by_id(user_browser.contents, 'templates_table')45 >>> table = find_tag_by_id(user_browser.contents, 'templates_table')
38 >>> print extract_text(table)46 >>> print extract_text(table)
39 Template name Actions47 Template name Last update Actions
40 at-the-top Download48 at-the-top ... Download
41 evolution-2.2 Download49 evolution-2.2 2005-08-25 Download
42 evolution-2.2-test Download50 evolution-2.2-test 2006-12-13 Download
4351
44If an administrator views this page, links to the templates admin page are52If an administrator views this page, links to the templates admin page are
45shown, too.53shown, too.
@@ -48,13 +56,14 @@
48 ... 'http://translations.launchpad.dev/evolution/trunk/+templates')56 ... 'http://translations.launchpad.dev/evolution/trunk/+templates')
49 >>> table = find_tag_by_id(admin_browser.contents, 'templates_table')57 >>> table = find_tag_by_id(admin_browser.contents, 'templates_table')
50 >>> print extract_text(table)58 >>> print extract_text(table)
51 Template name Actions59 Template name Last update Actions
52 at-the-top Edit Upload Download Administer60 at-the-top ... Edit Upload Download Administer
53 evolution-2.2 Edit Upload Download Administer61 evolution-2.2 2005-08-25 Edit Upload Download Administer
54 evolution-2.2-test Edit Upload Download Administer62 evolution-2.2-test 2006-12-13 Edit Upload Download Administer
5563
5664
57== Links to the templates ==65Links to the templates
66----------------------
5867
59Clicking on a template name will take the user to that template's overview68Clicking on a template name will take the user to that template's overview
60page.69page.
6170
=== modified file 'lib/lp/translations/stories/standalone/xx-language.txt'
--- lib/lp/translations/stories/standalone/xx-language.txt 2009-10-31 11:06:44 +0000
+++ lib/lp/translations/stories/standalone/xx-language.txt 2009-12-07 21:11:12 +0000
@@ -1,6 +1,15 @@
1
2
3Languages view
4==============
5
1Here is the tale of languages. We will see how to create, find and edit6Here is the tale of languages. We will see how to create, find and edit
2them.7them.
38
9
10Getting there
11-------------
12
4Launchpad Translations has a main page.13Launchpad Translations has a main page.
514
6 >>> admin_browser.open('http://translations.launchpad.dev/')15 >>> admin_browser.open('http://translations.launchpad.dev/')
@@ -11,7 +20,12 @@
11 >>> print admin_browser.url20 >>> print admin_browser.url
12 http://translations.launchpad.dev/+languages21 http://translations.launchpad.dev/+languages
1322
14Following the link, there is a form to add new languages.23
24Adding new languages
25--------------------
26
27Following the link from the translations main page, there is a form to
28add new languages.
1529
16 >>> admin_browser.getLink('Add new language').click()30 >>> admin_browser.getLink('Add new language').click()
17 >>> print admin_browser.url31 >>> print admin_browser.url
@@ -65,11 +79,16 @@
65 ...79 ...
66 LinkNotFoundError80 LinkNotFoundError
6781
68 >>> user_browser.open('http://translations.launchpad.dev/+languages/+add')82 >>> user_browser.open(
83 ... 'http://translations.launchpad.dev/+languages/+add')
69 Traceback (most recent call last):84 Traceback (most recent call last):
70 ...85 ...
71 Unauthorized:...86 Unauthorized:...
7287
88
89Searching for a language
90------------------------
91
73From the top languages page, anyone can find languages.92From the top languages page, anyone can find languages.
7493
75 >>> browser.open('http://translations.launchpad.dev/+languages')94 >>> browser.open('http://translations.launchpad.dev/+languages')
@@ -82,7 +101,11 @@
82 >>> print browser.url101 >>> print browser.url
83 http://translations.launchpad.dev/+languages/+index?find=Spanish102 http://translations.launchpad.dev/+languages/+index?find=Spanish
84103
85And following one of the found languages, we can see a brief information104
105Read language information
106-------------------------
107
108Following one of the found languages, we can see a brief information
86about the selected language.109about the selected language.
87110
88 >>> browser.getLink('Spanish').click()111 >>> browser.getLink('Spanish').click()
@@ -128,14 +151,50 @@
128 ...Uruguay...151 ...Uruguay...
129 ...Venezuela...152 ...Venezuela...
130153
131 >>> topcontributors_portlet = find_portlet(browser.contents, 'Top contributors')154 >>> topcontributors_portlet = find_portlet(
155 ... browser.contents, 'Top contributors')
132 >>> print topcontributors_portlet156 >>> print topcontributors_portlet
133 <...157 <...
134 ...Carlos Perelló Marín...158 ...Carlos Perelló Marín...
135159
160Our test sample data does not know about plural forms of
161Abkhazian and about countries where this language is spoken.
162
163We will see a note about missing plural forms and a link to Rosetta
164add question page for informing Rosetta admin about the right plural
165form.
166
167 >>> browser.open('http://translations.launchpad.dev/+languages/ab')
168 >>> print extract_text(find_portlet(browser.contents, 'Plural forms'
169 ... ).renderContents())
170 Plural forms
171 Unfortunately, Launchpad doesn't know the plural
172 form information for this language...
173
174 >>> print browser.getLink(id='plural_question').url
175 http://answers.launchpad.dev/rosetta/+addquestion
176
177We will see a note that Launchpad does not know in which countries
178this language is spoken and a link to add question page for informing
179Rosetta admin about the countries where this page is officially spoken.
180
181 >>> countries_portlet = find_portlet(browser.contents, 'Countries')
182 >>> print countries_portlet
183 <...
184 Abkhazian is not registered as being spoken in any
185 country...
186
187 >>> print browser.getLink(id='country_question').url
188 http://answers.launchpad.dev/rosetta/+addquestion
189
190
191Edit language information
192-------------------------
193
136Finally, there is the edit form to change language basic information.194Finally, there is the edit form to change language basic information.
137195
138 >>> user_browser.open('http://translations.launchpad.dev/+languages/es')196 >>> user_browser.open(
197 ... 'http://translations.launchpad.dev/+languages/es')
139 >>> print user_browser.url198 >>> print user_browser.url
140 http://translations.launchpad.dev/+languages/es199 http://translations.launchpad.dev/+languages/es
141200
@@ -146,7 +205,8 @@
146 ...205 ...
147 LinkNotFoundError206 LinkNotFoundError
148207
149 >>> user_browser.open('http://translations.launchpad.dev/+languages/es/+admin')208 >>> user_browser.open(
209 ... 'http://translations.launchpad.dev/+languages/es/+admin')
150 Traceback (most recent call last):210 Traceback (most recent call last):
151 ...211 ...
152 Unauthorized:...212 Unauthorized:...
@@ -155,7 +215,8 @@
155215
156 >>> from canonical.launchpad.testing.pages import strip_label216 >>> from canonical.launchpad.testing.pages import strip_label
157217
158 >>> admin_browser.open('http://translations.launchpad.dev/+languages/es')218 >>> admin_browser.open(
219 ... 'http://translations.launchpad.dev/+languages/es')
159 >>> print admin_browser.url220 >>> print admin_browser.url
160 http://translations.launchpad.dev/+languages/es221 http://translations.launchpad.dev/+languages/es
161222
162223
=== modified file 'lib/lp/translations/templates/language-index.pt'
--- lib/lp/translations/templates/language-index.pt 2009-09-17 14:45:59 +0000
+++ lib/lp/translations/templates/language-index.pt 2009-12-07 21:11:12 +0000
@@ -43,8 +43,10 @@
43 <p class="helpwanted">43 <p class="helpwanted">
44 Unfortunately, Launchpad doesn't know the plural form44 Unfortunately, Launchpad doesn't know the plural form
45 information for this language. If you know it, please open a45 information for this language. If you know it, please open a
46 <a href="/rosetta/+addticket">ticket</a> with that information,46 <a id='plural_question'
47 so we can add it to Launchpad.47 tal:attributes="href view/add_question_url"
48 >question</a>
49 with that information, so we can add it to Launchpad.
48 </p>50 </p>
49 </tal:has_not_pluralforms>51 </tal:has_not_pluralforms>
50 </div>52 </div>
@@ -124,8 +126,11 @@
124 </tal:language>126 </tal:language>
125 is not registered as being spoken in any country. If you know127 is not registered as being spoken in any country. If you know
126 about a country that officially speaks this language, please128 about a country that officially speaks this language, please
127 open a <a href="/rosetta/+addticket">ticket</a> with that129 open a
128 information, so we can add it to Launchpad.130 <a id='country_question'
131 tal:attributes="href view/add_question_url"
132 >question</a>
133 with that information, so we can add it to Launchpad.
129 </p>134 </p>
130 </tal:has_not_countries>135 </tal:has_not_countries>
131 </div>136 </div>
132137
=== modified file 'lib/lp/translations/templates/object-templates.pt'
--- lib/lp/translations/templates/object-templates.pt 2009-11-24 19:23:52 +0000
+++ lib/lp/translations/templates/object-templates.pt 2009-12-07 21:11:12 +0000
@@ -26,16 +26,16 @@
26 </style>26 </style>
27 <style tal:condition="view/is_distroseries" type="text/css">27 <style tal:condition="view/is_distroseries" type="text/css">
28 #templates_table {28 #templates_table {
29 width: 72em;29 width: 79em;
30 }30 }
31 </style>31 </style>
32 <style tal:condition="not:view/is_distroseries" type="text/css">32 <style tal:condition="not:view/is_distroseries" type="text/css">
33 #templates_table {33 #templates_table {
34 width: 50em;34 width: 58em;
35 }35 }
36 </style>36 </style>
37 <script language="JavaScript" type="text/javascript">37 <script language="JavaScript" type="text/javascript">
38 YUI().use('node-base', 'event-delegate', function(Y) {38 LPS.use('node-base', 'event-delegate', function(Y) {
39 Y.on('domready', function(e) {39 Y.on('domready', function(e) {
40 Y.all('#templates_table .template_links').addClass(40 Y.all('#templates_table .template_links').addClass(
41 'inactive_links');41 'inactive_links');
@@ -75,6 +75,7 @@
75 <th tal:condition="view/is_distroseries"75 <th tal:condition="view/is_distroseries"
76 class="sourcepackage_column">Source package</th>76 class="sourcepackage_column">Source package</th>
77 <th class="template_column">Template name</th>77 <th class="template_column">Template name</th>
78 <th class="lastupdate_column">Last update</th>
78 <th class="actions_column"79 <th class="actions_column"
79 tal:condition="context/required:launchpad.AnyPerson">80 tal:condition="context/required:launchpad.AnyPerson">
80 Actions</th>81 Actions</th>
@@ -88,6 +89,22 @@
88 </td>89 </td>
89 <td class="template_column"><a tal:attributes="href template/fmt:url"90 <td class="template_column"><a tal:attributes="href template/fmt:url"
90 tal:content="template/name">Template name</a></td>91 tal:content="template/name">Template name</a></td>
92 <td class="lastupdate_column">
93 <span class="sortkey"
94 tal:condition="template/date_last_updated"
95 tal:content="template/date_last_updated/fmt:datetime">
96 time sort key
97 </span>
98 <span class="lastupdate_column"
99 tal:condition="template/date_last_updated"
100 tal:attributes="
101 title template/date_last_updated/fmt:datetime"
102 tal:content="
103 template/date_last_updated/fmt:approximatedate"
104 >
105 2009-09-23
106 </span>
107 </td>
91 <td class="actions_column"108 <td class="actions_column"
92 tal:condition="context/required:launchpad.AnyPerson">109 tal:condition="context/required:launchpad.AnyPerson">
93 <div class="template_links">110 <div class="template_links">
94111
=== modified file 'lib/lp/translations/templates/pofile-export.pt'
--- lib/lp/translations/templates/pofile-export.pt 2009-11-10 21:04:19 +0000
+++ lib/lp/translations/templates/pofile-export.pt 2009-12-07 21:11:12 +0000
@@ -13,7 +13,7 @@
13 }13 }
14 </style>14 </style>
15 <script type="text/javascript">15 <script type="text/javascript">
16 YUI().use('node', 'event', function(Y){16 LPS.use('node', 'event', function(Y){
17 Y.on('domready', function(){17 Y.on('domready', function(){
18 // The pochanged option is only available for the PO format.18 // The pochanged option is only available for the PO format.
19 var formatlist = Y.one('#div_format select');19 var formatlist = Y.one('#div_format select');
2020
=== modified file 'lib/lp/translations/templates/pofile-translate.pt'
--- lib/lp/translations/templates/pofile-translate.pt 2009-11-04 19:59:16 +0000
+++ lib/lp/translations/templates/pofile-translate.pt 2009-12-07 21:11:12 +0000
@@ -20,7 +20,7 @@
20 <script type="text/javascript">20 <script type="text/javascript">
21 registerLaunchpadFunction(insertAllExpansionButtons);21 registerLaunchpadFunction(insertAllExpansionButtons);
2222
23 YUI().use('node', 'cookie', 'anim', 'lp.pofile', function(Y) {23 LPS.use('node', 'cookie', 'anim', 'lp.pofile', function(Y) {
2424
25 var hide_notification = function(node) {25 var hide_notification = function(node) {
26 var hide_anim = new Y.Anim({26 var hide_anim = new Y.Anim({
2727
=== modified file 'lib/lp/translations/templates/translation-import-queue-macros.pt'
--- lib/lp/translations/templates/translation-import-queue-macros.pt 2009-11-20 14:15:34 +0000
+++ lib/lp/translations/templates/translation-import-queue-macros.pt 2009-12-07 21:11:12 +0000
@@ -18,7 +18,7 @@
18 </script>18 </script>
1919
20 <script type="text/javascript">20 <script type="text/javascript">
21 YUI().use( 'translations', 'event', function(Y) {21 LPS.use( 'translations', 'event', function(Y) {
22 Y.on('domready', function(e) {22 Y.on('domready', function(e) {
23 Y.translations.initialize_import_queue_page(Y);23 Y.translations.initialize_import_queue_page(Y);
24 });24 });
2525
=== modified file 'lib/lp/translations/templates/translationimportqueueentry-index.pt'
--- lib/lp/translations/templates/translationimportqueueentry-index.pt 2009-11-04 13:56:17 +0000
+++ lib/lp/translations/templates/translationimportqueueentry-index.pt 2009-12-07 21:11:12 +0000
@@ -14,7 +14,7 @@
14 }14 }
15 </style>15 </style>
16 <script type="text/javascript">16 <script type="text/javascript">
17 YUI().use('node', 'lazr.anim', function(Y) {17 LPS.use('node', 'lazr.anim', function(Y) {
18 var fields = {'POT':18 var fields = {'POT':
19 ['field.name', 'field.translation_domain',19 ['field.name', 'field.translation_domain',
20 'field.languagepack'],20 'field.languagepack'],
2121
=== modified file 'lib/lp/translations/templates/translationmessage-translate.pt'
--- lib/lp/translations/templates/translationmessage-translate.pt 2009-09-17 07:28:30 +0000
+++ lib/lp/translations/templates/translationmessage-translate.pt 2009-12-07 21:11:12 +0000
@@ -18,7 +18,7 @@
18 tal:define="lp_js string:${icingroot}/build"18 tal:define="lp_js string:${icingroot}/build"
19 tal:attributes="src string:${lp_js}/translations/pofile.js"></script>19 tal:attributes="src string:${lp_js}/translations/pofile.js"></script>
20 <script type="text/javascript">20 <script type="text/javascript">
21 YUI().use('node', 'lp.pofile', function(Y) {21 LPS.use('node', 'lp.pofile', function(Y) {
22 Y.on('domready', Y.lp.pofile.setupSuggestionDismissal);22 Y.on('domready', Y.lp.pofile.setupSuggestionDismissal);
23 });23 });
24 </script>24 </script>

Subscribers

People subscribed via source and target branches

to status/vote changes: