Merge lp:~barry/launchpad/430068-person into lp:launchpad

Proposed by Barry Warsaw
Status: Merged
Merged at revision: not available
Proposed branch: lp:~barry/launchpad/430068-person
Merge into: lp:launchpad
Diff against target: None lines
To merge this branch: bzr merge lp:~barry/launchpad/430068-person
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code and ui Approve
Review via email: mp+11832@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Barry Warsaw (barry) wrote :
Download full text (3.5 KiB)

= Summary =

This branch converts three more pages to UI 3.0, the ~person/+codesofconduct
page, the ~person/+participation page, and the ~person/+teamhierarchy page.

I suck for letting this branch get over 800 lines.

== Proposed fix ==

Implement mostly mechanical changes to convert the relevant templates to UI
3.0.

== Pre-implementation notes ==

None really needed.

== Implementation details ==

In addition to the template changes, which were mostly mechanical and thus not
requiring a formal ui review, I cleaned up a lot of code and added some new
tests.

 * Removed some obsolete pagetitles.py entries.
 * Made CodeOfConductView inherit from LaunchpadView so that it could be
   simplified. I also added a page_title attribute to this view since it has
   no breadcrumbs (nor should it; it isn't actually traversable from a core
   content object).
 * Split off the view code for the above templates into separate view
   classes. The monolithic PersonView really sucks so it's better to have
   smaller, more focused views. Added PersonParticipationView and
   TeamHierarchyView.
 * Rewrote PersonParticipationView.indirect_teams_via to make it a property
   and to make the implementation more readable.
 * Fixed the crufty way signature records are updated in
   PersonCodeOfConductEditView. Previously, the changes were made by
   performCoCChanges() method called by placeholder TAL in the template.
   LaunchpadView supports an `initialize()` method, which makes much more
   sense for performing this work.
 * Added a bunch of convenience properties to TeamHierarchyView
 * Cleaned up 02-signcoc.txt doctest, but unfortunately did not convert this
   to an xx- random test.

== Tests ==

The new view tests:

  % bin/test -vv -t team-hierarchy-views -t xx-coc-views

Regression testing the code of conduct signing stories:

  % bin/test -vv -t gpg-coc

Regression testing the other template changes:

  % bin/test -vv -m lp.registry -t stories

== Demo and Q/A ==

To view the new team hierarchy pages with various combinations of sub- and
super- team structure:

 * http://launchpad.dev/~warty-gnome/+teamhierarchy
 * http://launchpad.dev/~ubuntu-team/+teamhierarchy
 * http://launchpad.dev/~guadamen/+teamhierarchy

To view the changes to the participation pages:

 * http://launchpad.dev/~name12/+participation
 * http://launchpad.dev/~name16/+participation

To view the new code of conduct pages:

 * http://launchpad.dev/~name12/+codesofconduct
 * http://launchpad.dev/~no-priv/+codesofconduct
 * http://launchpad.dev/codeofconduct
 * http://launchpad.dev/codeofconduct/1.0/+sign
 * http://launchpad.dev/codeofconduct/1.0.1

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/lp/registry/browser/configure.zcml
  lib/lp/registry/doc/team-hierarchy-views.txt
  lib/lp/registry/doc/xx-coc-views.txt
  lib/lp/registry/templates/person-teamhierarchy.pt
  lib/canonical/launchpad/pagetitles.py
  lib/lp/registry/browser/team.py
  lib/lp/registry/browser/codeofconduct.py
  lib/lp/registry/templates/person-codesofconduct.pt
  lib/lp/registry/t...

Read more...

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

Thanks for doing the extra clean up. This branch is fine to land as is.

> === modified file 'lib/lp/registry/stories/gpg-coc/02-signcoc.txt'
> --- lib/lp/registry/stories/gpg-coc/02-signcoc.txt 2009-08-18 17:25:41 +0000
> +++ lib/lp/registry/stories/gpg-coc/02-signcoc.txt 2009-09-15 20:20:20 +0000

...

> +Code of Conduct registration problems
> +=====================================
> +
> +Sample Person tries unsuccessfully to register a truncated code of conduct.
> +
> + >>> truncated_coc = read_file('truncated_coc.asc')
> + >>> browser.open('http://launchpad.dev/codeofconduct/1.0.1/+sign')
> + >>> browser.getControl('Signed Code').value = truncated_coc
> + >>> browser.getControl('Continue').click()
> + >>> print_errors(browser.contents)
> + There is 1 error.
> + The signed text does not match the Code of Conduct. Make sure that you
> + signed the correct text (white space differences are acceptable).
> +
> +Sample Person tries unsuccessfully to register an old version of the code.
> +
> + >>> coc_version_1_0 = read_file('10_coc.asc')
> + >>> browser.getControl('Signed Code').value = coc_version_1_0
> + >>> browser.getControl('Continue').click()
> + >>> print_errors(browser.contents)
> + There is 1 error.
> + The signed text does not match the Code of Conduct. Make sure that you
> + signed the correct text (white space differences are acceptable).

These two tests should be view tests, but I think we can do that when we
add support for launchpad and U1 hacking.
...

review: Approve (code and ui)
Revision history for this message
Barry Warsaw (barry) wrote :

On Sep 16, 2009, at 03:47 PM, Curtis Hovey wrote:

>Review: Approve code and ui
>Thanks for doing the extra clean up. This branch is fine to land as is.

Thanks!

>> +Code of Conduct registration problems
>> +=====================================
>> +
>> +Sample Person tries unsuccessfully to register a truncated code of conduct.
>> +
>> + >>> truncated_coc = read_file('truncated_coc.asc')
>> + >>> browser.open('http://launchpad.dev/codeofconduct/1.0.1/+sign')
>> + >>> browser.getControl('Signed Code').value = truncated_coc
>> + >>> browser.getControl('Continue').click()
>> + >>> print_errors(browser.contents)
>> + There is 1 error.
>> + The signed text does not match the Code of Conduct. Make sure that you
>> + signed the correct text (white space differences are acceptable).
>> +
>> +Sample Person tries unsuccessfully to register an old version of the code.
>> +
>> + >>> coc_version_1_0 = read_file('10_coc.asc')
>> + >>> browser.getControl('Signed Code').value = coc_version_1_0
>> + >>> browser.getControl('Continue').click()
>> + >>> print_errors(browser.contents)
>> + There is 1 error.
>> + The signed text does not match the Code of Conduct. Make sure that you
>> + signed the correct text (white space differences are acceptable).
>
>These two tests should be view tests, but I think we can do that when we
>add support for launchpad and U1 hacking.

Agreed.
-Barry

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/pagetitles.py'
2--- lib/canonical/launchpad/pagetitles.py 2009-09-15 10:05:14 +0000
3+++ lib/canonical/launchpad/pagetitles.py 2009-09-15 23:18:27 +0000
4@@ -314,8 +314,6 @@
5
6 codeofconduct_admin = 'Administer Codes of Conduct'
7
8-codeofconduct_index = ContextTitle('%s')
9-
10 codeofconduct_list = 'Ubuntu Codes of Conduct'
11
12 def contact_user(context, view):
13@@ -704,8 +702,6 @@
14
15 person_packagebugs_search = person_packagebugs
16
17-person_participation = ContextTitle("Team participation by %s")
18-
19 person_review = ContextDisplayName("Review %s")
20
21 person_specfeedback = ContextDisplayName('Feature feedback requests for %s')
22@@ -715,8 +711,6 @@
23 person_translations_to_review = ContextDisplayName(
24 'Translations for review by %s')
25
26-person_teamhierarchy = ContextDisplayName('Team hierarchy for %s')
27-
28 pofile_index = ContextTitle(smartquote('Translation overview for "%s"'))
29
30 def pofile_translate(context, view):
31
32=== modified file 'lib/lp/registry/browser/codeofconduct.py'
33--- lib/lp/registry/browser/codeofconduct.py 2009-08-18 17:15:10 +0000
34+++ lib/lp/registry/browser/codeofconduct.py 2009-09-15 20:20:20 +0000
35@@ -110,12 +110,14 @@
36 return Link('../', text, icon='info')
37
38
39-class CodeOfConductView:
40+class CodeOfConductView(LaunchpadView):
41 """Simple view class for CoC page."""
42
43- def __init__(self, context, request):
44- self.context = context
45- self.request = request
46+ @property
47+ def page_title(self):
48+ """See `LaunchpadView`."""
49+ # This page has no breadcrumbs, nor should it.
50+ return self.context.title
51
52
53 class CodeOfConductDownloadView:
54
55=== modified file 'lib/lp/registry/browser/configure.zcml'
56--- lib/lp/registry/browser/configure.zcml 2009-09-15 16:26:15 +0000
57+++ lib/lp/registry/browser/configure.zcml 2009-09-15 22:55:30 +0000
58@@ -848,14 +848,18 @@
59 name="+karma"
60 template="../templates/person-karma.pt"/>
61 </browser:pages>
62+ <browser:page
63+ for="lp.registry.interfaces.person.IPerson"
64+ permission="zope.Public"
65+ name="+participation"
66+ class="lp.registry.browser.person.PersonParticipationView"
67+ template="../templates/person-participation.pt"
68+ />
69 <browser:pages
70 for="lp.registry.interfaces.person.IPerson"
71 permission="zope.Public"
72 class="lp.registry.browser.person.PersonView">
73 <browser:page
74- name="+participation"
75- template="../templates/person-participation.pt"/>
76- <browser:page
77 name="+sshkeys"
78 attribute="showSSHKeys"/>
79 <browser:page
80@@ -1032,7 +1036,7 @@
81 <browser:page
82 for="lp.registry.interfaces.person.IPerson"
83 permission="zope.Public"
84- class="lp.registry.browser.person.PersonView"
85+ class="lp.registry.browser.team.TeamHierarchyView"
86 name="+teamhierarchy"
87 template="../templates/person-teamhierarchy.pt"/>
88 <browser:page
89
90=== modified file 'lib/lp/registry/browser/person.py'
91--- lib/lp/registry/browser/person.py 2009-09-15 19:28:54 +0000
92+++ lib/lp/registry/browser/person.py 2009-09-15 21:19:06 +0000
93@@ -6,8 +6,6 @@
94 """Person-related view classes."""
95
96 __metaclass__ = type
97-
98-
99 __all__ = [
100 'BeginTeamClaimView',
101 'BugSubscriberPackageBugsSearchListingView',
102@@ -86,6 +84,7 @@
103 'archive_to_person',
104 ]
105
106+
107 import cgi
108 import copy
109 import itertools
110@@ -239,6 +238,8 @@
111 from lp.answers.interfaces.questioncollection import IQuestionSet
112 from lp.answers.interfaces.questionsperson import IQuestionsPerson
113
114+COMMASPACE = ', '
115+
116
117 class RestrictedMembershipsPersonView(LaunchpadView):
118 """Secure access to team membership information for a person.
119@@ -2831,17 +2832,6 @@
120 assert self.user is not None
121 return self.user.findPathToTeam(self.context)
122
123- def indirect_teams_via(self):
124- """Return a list of dictionaries, where each dictionary has a team
125- in which the person is an indirect member, and a path to membership
126- in that team.
127- """
128- return [{'team': team,
129- 'via': ', '.join(
130- [viateam.displayname for viateam in
131- self.context.findPathToTeam(team)[:-1]])}
132- for team in self.context.teams_indirectly_participated_in]
133-
134 def userIsParticipant(self):
135 """Return true if the user is a participant of this team.
136
137@@ -2975,6 +2965,31 @@
138 return False
139
140
141+class PersonParticipationView(LaunchpadView):
142+ """View for the ~person/+participation page."""
143+
144+ @property
145+ def label(self):
146+ return 'Team participation for ' + self.context.displayname
147+
148+ @property
149+ def indirect_teams_via(self):
150+ """Information about indirect membership.
151+
152+ :return: A list of dictionaries, where each dictionary has a team in
153+ which the person is an indirect member, and a path to membership in
154+ that team.
155+ :rtype: a list of dictionaries
156+ """
157+ indirect_teams = []
158+ for team in self.context.teams_indirectly_participated_in:
159+ via = COMMASPACE.join(viateam.displayname
160+ for viateam
161+ in self.context.findPathToTeam(team)[:-1])
162+ indirect_teams.append(dict(team=team, via=via))
163+ return indirect_teams
164+
165+
166 class EmailAddressVisibleState:
167 """The state of a person's email addresses w.r.t. the logged in user.
168
169@@ -3183,28 +3198,29 @@
170
171
172 class PersonCodeOfConductEditView(LaunchpadView):
173-
174- def performCoCChanges(self):
175- """Make changes to code-of-conduct signature records for this
176- person.
177- """
178+ """View for the ~person/+codesofconduct pages."""
179+
180+ @property
181+ def label(self):
182+ """See `LaunchpadView`."""
183+ return 'Codes of Conduct for ' + self.context.displayname
184+
185+ def initialize(self):
186+ """See `LaunchpadView`."""
187+ # Make changes to code-of-conduct signature records for this person.
188 sig_ids = self.request.form.get("DEACTIVATE_SIGNATURE")
189
190 if sig_ids is not None:
191 sCoC_util = getUtility(ISignedCodeOfConductSet)
192-
193- # verify if we have multiple entries to deactive
194+ # Verify that we have multiple entries to deactive.
195 if not isinstance(sig_ids, list):
196 sig_ids = [sig_ids]
197-
198 for sig_id in sig_ids:
199 sig_id = int(sig_id)
200- # Deactivating signature
201+ # Deactivating signature.
202 comment = 'Deactivated by Owner'
203 sCoC_util.modifySignature(sig_id, self.user, comment, False)
204
205- return True
206-
207
208 class PersonEditWikiNamesView(LaunchpadView):
209
210
211=== modified file 'lib/lp/registry/browser/team.py'
212--- lib/lp/registry/browser/team.py 2009-09-15 10:05:14 +0000
213+++ lib/lp/registry/browser/team.py 2009-09-15 22:55:30 +0000
214@@ -10,15 +10,17 @@
215 'TeamBrandingView',
216 'TeamContactAddressView',
217 'TeamEditView',
218+ 'TeamHierarchyView',
219 'TeamMailingListConfigurationView',
220 'TeamMailingListModerationView',
221 'TeamMailingListSubscribersView',
222+ 'TeamMapData',
223 'TeamMapView',
224- 'TeamMapData',
225 'TeamMemberAddView',
226 'TeamPrivacyAdapter',
227 ]
228
229+
230 from urllib import quote
231 from datetime import datetime
232 import math
233@@ -1106,3 +1108,31 @@
234 'content-type', 'application/xml;charset=utf-8')
235 body = LaunchpadView.render(self)
236 return body.encode('utf-8')
237+
238+
239+class TeamHierarchyView(LaunchpadView):
240+ """View for ~team/+teamhierarchy page."""
241+
242+ @property
243+ def label(self):
244+ return 'Team relationships for ' + self.context.displayname
245+
246+ @property
247+ def has_sub_teams(self):
248+ return self.context.sub_teams.count() > 0
249+
250+ @property
251+ def has_super_teams(self):
252+ return self.context.super_teams.count() > 0
253+
254+ @property
255+ def has_only_super_teams(self):
256+ return self.has_super_teams and not self.has_sub_teams
257+
258+ @property
259+ def has_only_sub_teams(self):
260+ return not self.has_super_teams and self.has_sub_teams
261+
262+ @property
263+ def has_relationships(self):
264+ return self.has_sub_teams or self.has_super_teams
265
266=== added file 'lib/lp/registry/doc/team-hierarchy-views.txt'
267--- lib/lp/registry/doc/team-hierarchy-views.txt 1970-01-01 00:00:00 +0000
268+++ lib/lp/registry/doc/team-hierarchy-views.txt 2009-09-15 22:55:30 +0000
269@@ -0,0 +1,100 @@
270+====================
271+Team hierarchy views
272+====================
273+
274+Team hierarchies are displayed via the ~team/+teamhierarchy page. By default,
275+brand new teams have no relationships at all.
276+
277+ >>> team = factory.makeTeam(displayname='Brand New Team')
278+ >>> view = create_initialized_view(team, '+teamhierarchy')
279+ >>> print view.label
280+ Team relationships for Brand New Team
281+
282+ >>> view.has_relationships
283+ False
284+ >>> view.has_sub_teams
285+ False
286+ >>> view.has_super_teams
287+ False
288+ >>> view.has_only_sub_teams
289+ False
290+ >>> view.has_only_super_teams
291+ False
292+
293+
294+Only sub-teams
295+==============
296+
297+Guadamen is related to some other teams.
298+
299+ >>> from lp.registry.interfaces.person import IPersonSet
300+ >>> person_set = getUtility(IPersonSet)
301+
302+ >>> guadamen = person_set.getByName('guadamen')
303+ >>> view = create_initialized_view(guadamen, '+teamhierarchy')
304+ >>> print view.label
305+ Team relationships for GuadaMen
306+ >>> view.has_relationships
307+ True
308+
309+Guadamen has sub-teams...
310+
311+ >>> view.has_sub_teams
312+ True
313+
314+...but no super teams.
315+
316+ >>> view.has_super_teams
317+ False
318+
319+In other words, Guadamen has only sub-teams.
320+
321+ >>> view.has_only_super_teams
322+ False
323+
324+ >>> view.has_only_sub_teams
325+ True
326+
327+
328+Only super-teams
329+================
330+
331+The Warty Gnome Team has only super-teams.
332+
333+ >>> warty = person_set.getByName('warty-gnome')
334+ >>> view = create_initialized_view(warty, '+teamhierarchy')
335+ >>> print view.label
336+ Team relationships for Warty Gnome Team
337+
338+ >>> view.has_super_teams
339+ True
340+ >>> view.has_sub_teams
341+ False
342+ >>> view.has_only_super_teams
343+ True
344+ >>> view.has_only_sub_teams
345+ False
346+ >>> view.has_relationships
347+ True
348+
349+
350+Both super-teams and sub-teams
351+==============================
352+
353+The Ubuntu Team has both sub-teams and super-teams.
354+
355+ >>> ubuntu = person_set.getByName('ubuntu-team')
356+ >>> view = create_initialized_view(ubuntu, '+teamhierarchy')
357+ >>> print view.label
358+ Team relationships for Ubuntu Team
359+
360+ >>> view.has_super_teams
361+ True
362+ >>> view.has_sub_teams
363+ True
364+ >>> view.has_only_super_teams
365+ False
366+ >>> view.has_only_sub_teams
367+ False
368+ >>> view.has_relationships
369+ True
370
371=== added file 'lib/lp/registry/doc/xx-coc-views.txt'
372--- lib/lp/registry/doc/xx-coc-views.txt 1970-01-01 00:00:00 +0000
373+++ lib/lp/registry/doc/xx-coc-views.txt 2009-09-15 20:20:20 +0000
374@@ -0,0 +1,12 @@
375+======================
376+Codes of conduct views
377+======================
378+
379+The PersonCodeOfConductEditView controls ~person/+codesofconduct. It displays
380+the person's name in the page label.
381+
382+ >>> login('admin@canonical.com')
383+ >>> geddy = factory.makePerson(displayname='Geddy Lee')
384+ >>> view = create_initialized_view(geddy, '+codesofconduct')
385+ >>> print view.label
386+ Codes of Conduct for Geddy Lee
387
388=== modified file 'lib/lp/registry/stories/gpg-coc/02-signcoc.txt'
389--- lib/lp/registry/stories/gpg-coc/02-signcoc.txt 2009-08-18 17:25:41 +0000
390+++ lib/lp/registry/stories/gpg-coc/02-signcoc.txt 2009-09-15 20:20:20 +0000
391@@ -1,72 +1,95 @@
392- First we check that Sample Person never signed a code of conduct.
393-
394- >>> browser.addHeader("Authorization", "Basic test@canonical.com:test")
395- >>> browser.open("http://launchpad.dev/~name12/+codesofconduct")
396-
397- >>> "Sample Person has never signed" in browser.contents
398- True
399-
400-
401- First we define a helper function
402-
403- >>> import os.path
404- >>> def filename_path(filename):
405- ... return os.path.join(os.path.dirname(__file__), filename)
406- ...
407-
408- Then we try to register a code of conduct with a truncated CoC:
409-
410- >>> filename = filename_path("truncated_coc.asc")
411- >>> truncated_coc = open(filename).read()
412-
413- >>> browser.open("http://launchpad.dev/codeofconduct/1.0.1/+sign")
414- >>> browser.getControl("Signed Code").value = truncated_coc
415- >>> browser.getControl("Continue").click()
416-
417- >>> message = (
418- ... "The signed text does not match the Code of Conduct. Make sure that "
419- ... "you signed the correct text (white space differences are acceptable")
420- >>> message in browser.contents
421- True
422-
423- We try to register an old version of the code (version 1.0)
424-
425- >>> filename = filename_path("10_coc.asc")
426- >>> old_coc = open(filename).read()
427- >>> browser.getControl("Signed Code").value = old_coc
428- >>> browser.getControl("Continue").click()
429-
430- >>> message in browser.contents
431- True
432-
433- If we try to access the old version page to sign it
434- >>> browser.open("http://launchpad.dev/codeofconduct/1.0/+sign")
435-
436- We're presented with a link to the current code page
437- >>> current_coc_link = browser.getLink("the current version")
438- >>> current_coc_link.url
439- 'http://launchpad.dev/codeofconduct/1.0.1'
440-
441- We navigate to the current Code page
442- >>> current_coc_link.click()
443- >>> browser.getLink("Sign it").click()
444- >>> browser.url
445- 'http://launchpad.dev/codeofconduct/1.0.1/+sign'
446-
447-
448- Now we try to register the code of conduct, using an reformatted copy
449- (leading spaces removed).
450-
451- >>> filename = filename_path("reformatted_101_coc.asc")
452- >>> reformatted_coc = open(filename).read()
453- >>> browser.getControl("Signed Code").value = reformatted_coc
454- >>> browser.getControl("Continue").click()
455-
456- This succeeds because the words are the same and in the same order.
457-
458- >>> browser.url
459- 'http://launchpad.dev/~name12/+codesofconduct'
460-
461- >>> browser.open("http://launchpad.dev/~name12/+codesofconduct")
462- >>> "1024D/DFD20543" in browser.contents
463- True
464+=========================
465+Signing a Code of Conduct
466+=========================
467+
468+Sample person has never signed a code of conduct.
469+
470+ >>> browser = setupBrowser(auth='Basic test@canonical.com:test')
471+ >>> browser.open('http://launchpad.dev/~name12/+codesofconduct')
472+ >>> print extract_text(find_main_content(browser.contents))
473+ Codes of Conduct for Sample Person
474+ Sample Person +codesofconduct
475+ Launchpad records codes of conduct you sign as commitments to the
476+ principles of collaboration, tolerance and open communication that
477+ drive the open source community.
478+ Sample Person has never signed a code
479+ of conduct.
480+ See or sign new code of conduct releases
481+
482+ # A helper function for reading a code-of-conduct file.
483+ >>> import os
484+ >>> def read_file(filename):
485+ ... path = os.path.join(os.path.dirname(__file__), filename)
486+ ... file_object = open(path)
487+ ... # Change this to a with-statement when we switch to Python 2.5.
488+ ... try:
489+ ... return file_object.read()
490+ ... finally:
491+ ... file_object.close()
492+
493+
494+Code of Conduct registration problems
495+=====================================
496+
497+Sample Person tries unsuccessfully to register a truncated code of conduct.
498+
499+ >>> truncated_coc = read_file('truncated_coc.asc')
500+ >>> browser.open('http://launchpad.dev/codeofconduct/1.0.1/+sign')
501+ >>> browser.getControl('Signed Code').value = truncated_coc
502+ >>> browser.getControl('Continue').click()
503+ >>> print_errors(browser.contents)
504+ There is 1 error.
505+ The signed text does not match the Code of Conduct. Make sure that you
506+ signed the correct text (white space differences are acceptable).
507+
508+Sample Person tries unsuccessfully to register an old version of the code.
509+
510+ >>> coc_version_1_0 = read_file('10_coc.asc')
511+ >>> browser.getControl('Signed Code').value = coc_version_1_0
512+ >>> browser.getControl('Continue').click()
513+ >>> print_errors(browser.contents)
514+ There is 1 error.
515+ The signed text does not match the Code of Conduct. Make sure that you
516+ signed the correct text (white space differences are acceptable).
517+
518+
519+Sample Person tries to access the old version page to sign it, and is informed
520+that there is a new version available.
521+
522+ >>> browser.open('http://launchpad.dev/codeofconduct/1.0/+sign')
523+ >>> browser.getLink('the current version').click()
524+ >>> print browser.url
525+ http://launchpad.dev/codeofconduct/1.0.1
526+
527+ >>> browser.getLink('Sign it').click()
528+ >>> print browser.url
529+ http://launchpad.dev/codeofconduct/1.0.1/+sign
530+
531+
532+Code of Conduct registration
533+============================
534+
535+Sample Person registers the code of conduct, using a reformatted copy which
536+has leading spaces removed. This succeeds because the words the same and
537+appear in the same order.
538+
539+ >>> reformatted_coc = read_file('reformatted_101_coc.asc')
540+ >>> browser.getControl('Signed Code').value = reformatted_coc
541+ >>> browser.getControl('Continue').click()
542+ >>> print browser.url
543+ http://launchpad.dev/~name12/+codesofconduct
544+
545+And now Sample Person's Codes of Conduct page shows that he's signed it.
546+
547+ >>> browser.open('http://launchpad.dev/~name12/+codesofconduct')
548+ >>> print extract_text(find_main_content(browser.contents))
549+ Codes of Conduct for Sample Person
550+ Sample Person +codesofconduct
551+ Launchpad records codes of conduct you sign as commitments to the
552+ principles of collaboration, tolerance and open communication that
553+ drive the open source community.
554+ Active signatures
555+ If you change your mind about agreeing to a code of conduct,
556+ you can deactivate your signature.
557+ 2009-09-15: digitally signed by Sample Person (1024D/DFD20543)
558+ ...
559
560=== modified file 'lib/lp/registry/templates/person-codesofconduct.pt'
561--- lib/lp/registry/templates/person-codesofconduct.pt 2009-07-17 17:59:07 +0000
562+++ lib/lp/registry/templates/person-codesofconduct.pt 2009-09-15 20:20:20 +0000
563@@ -3,81 +3,68 @@
564 xmlns:tal="http://xml.zope.org/namespaces/tal"
565 xmlns:metal="http://xml.zope.org/namespaces/metal"
566 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
567- xml:lang="en"
568- lang="en"
569- dir="ltr"
570- metal:use-macro="context/@@main_template/master"
571+ metal:use-macro="view/macro:page/main_only"
572 i18n:domain="launchpad"
573->
574-<body>
575-
576-<div metal:fill-slot="main">
577- <h1>Code of conduct signatures</h1>
578-
579- <tal:changes condition="view/performCoCChanges" />
580-
581- <p>Launchpad records codes of conduct you sign as commitments to the
582- principles of collaboration, tolerance and open communication that
583- drive the open source community.</p>
584-
585- <p tal:condition="not: context/signedcocs">
586- <tal:name replace="context/fmt:displayname"/> has never signed a code
587- of conduct.
588- </p>
589-
590- <div tal:condition="context/activesignatures">
591- <form name="coc" action="" method="post">
592-
593- <h2>Active signatures</h2>
594+ >
595+ <body>
596+ <div metal:fill-slot="main">
597+ <p>Launchpad records codes of conduct you
598+ sign as commitments to the principles of collaboration, tolerance and
599+ open communication that drive the open source community.</p>
600+
601+ <p tal:condition="not: context/signedcocs">
602+ <tal:name replace="context/fmt:displayname"/> has never signed a code
603+ of conduct.
604+ </p>
605+
606+ <div tal:condition="context/activesignatures">
607+ <form name="coc" action="" method="post">
608+ <h2>Active signatures</h2>
609 <p>If you change your mind about agreeing to a code of conduct,
610 you can deactivate your signature.</p>
611- <table>
612- <tbody>
613- <tr tal:repeat="sig context/activesignatures">
614- <td class="icon left">
615- <input
616- type="checkbox"
617- name="DEACTIVATE_SIGNATURE"
618- tal:attributes="value sig/id; id string:code${sig/id}"
619- />
620- </td>
621- <td>
622- <div>
623- <label
624- class="signature"
625- tal:attributes="for string:code${sig/id}"
626- tal:content="sig/displayname"
627- >blah</label>
628- </div>
629- <pre
630- class="discreet"
631- tal:content="sig/signedcode"
632- >SIGNATURE</pre>
633- </td>
634- </tr>
635- </tbody>
636- </table>
637- <input type="submit" value="Deactivate"/>
638- </form>
639- </div>
640-
641-
642- <div tal:condition="context/inactivesignatures">
643-
644- <h2>Inactive signatures</h2>
645-
646- <p>
647- Once <dfn>Inactive</dfn>, a signature can only be reactivated by a
648- <a href="/~admins">Launchpad Administrator</a>.
649- </p>
650+ <table>
651+ <tbody>
652+ <tr tal:repeat="sig context/activesignatures">
653+ <td class="icon left">
654+ <input
655+ type="checkbox"
656+ name="DEACTIVATE_SIGNATURE"
657+ tal:attributes="value sig/id; id string:code${sig/id}"
658+ />
659+ </td>
660+ <td>
661+ <div>
662+ <label
663+ class="signature"
664+ tal:attributes="for string:code${sig/id}"
665+ tal:content="sig/displayname"
666+ >blah</label>
667+ </div>
668+ <pre
669+ class="discreet"
670+ tal:content="sig/signedcode"
671+ >SIGNATURE</pre>
672+ </td>
673+ </tr>
674+ </tbody>
675+ </table>
676+ <input type="submit" value="Deactivate"/>
677+ </form>
678+ </div>
679+
680+ <div tal:condition="context/inactivesignatures">
681+ <h2>Inactive signatures</h2>
682+ <p>
683+ Once <dfn>Inactive</dfn>, a signature can only be reactivated by a
684+ <a href="/~admins">Launchpad Administrator</a>.
685+ </p>
686 <ul>
687 <li tal:repeat="sig context/inactivesignatures"
688 tal:content="sig/displayname"/>
689 </ul>
690+ </div>
691+
692+ <p><a href="/codeofconduct">See or sign new code of conduct releases</a></p>
693 </div>
694-
695- <p><a href="/codeofconduct">See or sign new code of conduct releases</a></p>
696-
697-</div>
698-</body>
699+ </body>
700 </html>
701
702=== modified file 'lib/lp/registry/templates/person-participation.pt'
703--- lib/lp/registry/templates/person-participation.pt 2009-07-17 17:59:07 +0000
704+++ lib/lp/registry/templates/person-participation.pt 2009-09-15 21:16:37 +0000
705@@ -3,83 +3,60 @@
706 xmlns:tal="http://xml.zope.org/namespaces/tal"
707 xmlns:metal="http://xml.zope.org/namespaces/metal"
708 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
709- xml:lang="en"
710- lang="en"
711- dir="ltr"
712- metal:use-macro="context/@@main_template/master"
713+ metal:use-macro="view/macro:page/main_only"
714 i18n:domain="launchpad"
715->
716+ >
717
718 <body>
719-
720-<div metal:fill-slot="help">
721- <p>
722- Launchpad has a sophisticated system for managing teams, including the
723- ability to create teams-of-teams. This page shows the teams that
724- <span tal:replace="context/title">Foo Bar</span> is a member of. You can
725- see the teams in which they are directly a member, and also the teams in
726- which they have indirect membership.
727- </p>
728- <p>
729- Indirect membership describes the case where a person is a member of a
730- team, which is in turn a member of another team. The person will then be
731- an indirect member of the other team.
732- </p>
733-</div>
734-
735-<metal:heading fill-slot="pageheading">
736- <h1>Team participation</h1>
737-</metal:heading>
738-
739-<div metal:fill-slot="main" tal:define="
740- direct context/myactivememberships;
741- indirect context/teams_indirectly_participated_in">
742-
743- <p tal:condition="direct/count">
744- <span tal:replace="context/title">Foo Bar</span>
745- is a member of the following teams:
746- </p>
747- <p tal:condition="not: direct/count">
748- <span tal:replace="context/title">Foo Bar</span>
749- has not yet joined any teams.
750- </p>
751-
752- <div class="left" tal:condition="direct/count">
753- <div class="portlet">
754- <h2>Direct membership</h2>
755- <table id="participation">
756- <tal:loop repeat="membership context/myactivememberships">
757- <tr tal:replace="structure membership/@@+listing-simple"
758- tal:condition="membership/team/@@+restricted-membership/userCanViewMembership"
759- />
760- </tal:loop>
761- </table>
762- </div>
763- </div>
764-
765- <div class="right" tal:condition="indirect/count">
766- <div class="portlet">
767- <h2>Indirect membership</h2>
768- <table id="indirect participation">
769- <tal:loop repeat="team_via view/indirect_teams_via">
770- <tr tal:condition="team_via/team/@@+restricted-membership/userCanViewMembership">
771- <td><img tal:replace="structure team_via/team/image:icon" />
772- </td>
773- <td>
774- <div>
775- <a tal:replace="structure team_via/team/fmt:link">Team name</a>
776- </div>
777- <div>
778- Via
779- <span tal:replace="team_via/via">guadamen</span>.
780- </div>
781- </td>
782- </tr>
783- </tal:loop>
784- </table>
785- </div>
786- </div>
787-
788-</div>
789+ <div metal:fill-slot="main"
790+ tal:define="direct context/myactivememberships;
791+ indirect context/teams_indirectly_participated_in">
792+
793+ <p tal:condition="direct/count">
794+ <span tal:replace="context/title">Foo Bar</span>
795+ is a member of the following teams:
796+ </p>
797+ <p tal:condition="not: direct/count">
798+ <span tal:replace="context/title">Foo Bar</span>
799+ has not yet joined any teams.
800+ </p>
801+
802+ <div class="left" tal:condition="direct/count">
803+ <div class="portlet">
804+ <h2>Direct membership</h2>
805+ <table id="participation">
806+ <tal:loop repeat="membership context/myactivememberships">
807+ <tr tal:replace="structure membership/@@+listing-simple"
808+ tal:condition="membership/team/@@+restricted-membership/userCanViewMembership"
809+ />
810+ </tal:loop>
811+ </table>
812+ </div>
813+ </div>
814+
815+ <div class="right" tal:condition="indirect/count">
816+ <div class="portlet">
817+ <h2>Indirect membership</h2>
818+ <table id="indirect participation">
819+ <tal:loop repeat="team_via view/indirect_teams_via">
820+ <tr tal:condition="team_via/team/@@+restricted-membership/userCanViewMembership">
821+ <td><img tal:replace="structure team_via/team/image:icon" />
822+ </td>
823+ <td>
824+ <div>
825+ <a tal:replace="structure team_via/team/fmt:link"
826+ >Team name</a>
827+ </div>
828+ <div>
829+ Via
830+ <span tal:replace="team_via/via">guadamen</span>.
831+ </div>
832+ </td>
833+ </tr>
834+ </tal:loop>
835+ </table>
836+ </div>
837+ </div>
838+ </div>
839 </body>
840 </html>
841
842=== modified file 'lib/lp/registry/templates/person-teamhierarchy.pt'
843--- lib/lp/registry/templates/person-teamhierarchy.pt 2009-07-17 17:59:07 +0000
844+++ lib/lp/registry/templates/person-teamhierarchy.pt 2009-09-15 22:55:30 +0000
845@@ -3,67 +3,54 @@
846 xmlns:tal="http://xml.zope.org/namespaces/tal"
847 xmlns:metal="http://xml.zope.org/namespaces/metal"
848 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
849- xml:lang="en"
850- lang="en"
851- dir="ltr"
852- metal:use-macro="context/@@main_template/master"
853+ metal:use-macro="view/macro:page/main_only"
854 i18n:domain="launchpad"
855->
856+ >
857
858 <body>
859-
860-<div metal:fill-slot="main">
861- <h1>
862- &#8220;<span tal:replace="context/displayname">
863- Foo</span>&#8221; team
864- </h1>
865- <h2>Relationship to other teams</h2>
866- <tal:block define="
867- subteams context/sub_teams;
868- superteams context/super_teams
869- ">
870- <p tal:condition="python: not subteams and not superteams">
871+ <div metal:fill-slot="main">
872+ <p tal:condition="not: view/has_relationships">
873 None of the members of
874 &#8220;<span tal:replace="context/displayname"/>&#8221;
875- are teams,
876- and nor is it a member of any other team.
877+ are teams, nor is it a member of any other team.
878 </p>
879- <p tal:condition="python: superteams and not subteams">
880+ <p tal:condition="view/has_only_super_teams">
881 None of the members of
882 &#8220;<span tal:replace="context/displayname"/>&#8221;
883 are teams.
884 </p>
885- <tal:block condition="subteams">
886+ <tal:block condition="view/has_sub_teams">
887 <p>
888 These teams are members of
889 &#8220;<span tal:replace="context/displayname"/>&#8221;:
890 </p>
891 <ul id="subteams">
892- <tal:subteams repeat="team subteams">
893- <li tal:condition="team/@@+restricted-membership/userCanViewMembership">
894+ <tal:subteams repeat="team context/sub_teams">
895+ <li
896+ tal:condition="team/@@+restricted-membership/userCanViewMembership">
897 <tal:link replace="structure team/fmt:link" />
898 </li>
899 </tal:subteams>
900- </ul>
901+ </ul>
902 </tal:block>
903- <p tal:condition="python: subteams and not superteams">
904+ <p tal:condition="view/has_only_sub_teams">
905 &#8220;<span tal:replace="context/displayname"/>&#8221;
906 is not a member of any other team.
907 </p>
908- <tal:block condition="superteams">
909+ <tal:block condition="view/has_super_teams">
910 <p>
911 &#8220;<span tal:replace="context/displayname"/>&#8221;
912 is a member of these teams:
913 </p>
914 <ul id="superteams">
915- <tal:superteams repeat="team superteams">
916- <li tal:condition="team/@@+restricted-membership/userCanViewMembership">
917+ <tal:superteams repeat="team context/super_teams">
918+ <li
919+ tal:condition="team/@@+restricted-membership/userCanViewMembership">
920 <tal:link replace="structure team/fmt:link" />
921 </li>
922 </tal:superteams>
923 </ul>
924 </tal:block>
925- </tal:block>
926-</div>
927+ </div>
928 </body>
929 </html>