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

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

= Summary =

This branch fixes bug 430065 which converts the ~person/+editemails and
~person/+changepassword pages to UI 3.0. Along the way, I was able to close
an old bug 180349 which adds some useful links to the mailing list
subscription section on the +editemails page.

== Proposed fix ==

Update the templates and views, fix tests, and do a minimal redesign on the
+editemails page. That page is actually pretty horrendous, but time boxing
only allows for a moderate redesign and improvement. In particular, on the
+editemails page it would be nice to move the Add button to next to the "Add a
new address" text box, but given the way the form is laid out, this isn't
possible.

The ui has been approved by rockstar and edwin.

== Pre-implementation notes ==

None really. By now, conversions are pretty straightforward.

== Implementation details ==

Changes in this branch:

* Get rid of the person-changepassword.pt template altogether, substituting
  generic-edit.pt for it.

* Removed some pagetitles that are no longer necessary.

* Fixed a stylistic nit in launchpad.py Hierarchy class.

* Cleaned up the PersonEditEmailsView implementation. Also, change the
  mailing_list_widgets property implementation to return a dictionary instead
  of the widgets directly. This allowed me to fix bug 180349.

* Update a bunch of doctests.

== Tests ==

% bin/test -vv -t mailinglist -t stories/foaf -t stories/gpg-coc -t conduct

== Demo and Q/A ==

For a person with no preferred email address and no mailing list
subscriptions:

    http://launchpad.dev/~matsubara/+editemails

For a person with a preferred email address but no mailing list subscriptions:

    http://launchpad.dev/~no-priv/+editemails

For a person with several validated email addresses and a mailing list
subscription:

    http://launchpad.dev/~name16/+editemails

To see the change password page:

    http://launchpad.dev/~no-priv/+changepassword

= 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/stories/mailinglists/subscriptions.txt
  lib/canonical/launchpad/pagetitles.py
  lib/lp/registry/stories/foaf/xx-validate-email.txt
  lib/lp/registry/templates/person-editemails.pt
  lib/canonical/launchpad/browser/launchpad.py
  lib/lp/registry/stories/foaf/xx-setpreferredemail.txt
  lib/lp/registry/browser/person.py

== Pylint notices ==

lib/lp/registry/browser/person.py
    117: [F0401] Unable to import 'lazr.delegates' (No module named delegates)
    118: [F0401] Unable to import 'lazr.config' (No module named config)
    119: [F0401] Unable to import 'lazr.restful.interface' (No module named restful)

Revision history for this message
Brad Crittenden (bac) wrote :
Download full text (6.6 KiB)

Hi Barry,

The changes look good. Thanks for taking the time to make a lot of drive-by fixes.

I've got a few questions below.

--Brad

> === modified file 'lib/lp/registry/browser/person.py'
> --- lib/lp/registry/browser/person.py 2009-09-16 17:07:14 +0000
> +++ lib/lp/registry/browser/person.py 2009-09-16 21:19:47 +0000
> @@ -4086,6 +4086,8 @@
> custom_widget('mailing_list_auto_subscribe_policy',
> LaunchpadRadioWidgetWithDescription)
>
> + label = 'Change your e-mail settings'
> +
> def initialize(self):
> if self.context.is_team:
> # +editemails is not available on teams.
> @@ -4191,13 +4193,11 @@
> which the user is subscribed to this mailing list.
> """
> subscription = mailing_list.getSubscription(self.context)
> - if subscription is not None:
> - if subscription.email_address is None:
> - return "Preferred address"
> - else:
> - return subscription.email_address
> - else:
> + if subscription is None:
> return "Don't subscribe"
> + if subscription.email_address is None:

Hmm, I'd have used an elif.

> + return 'Preferred address'
> + return subscription.email_address
>
> def _mailing_list_fields(self):
> """Creates a field for each mailing list the user can subscribe to.
> @@ -4233,8 +4235,22 @@
> @property
> def mailing_list_widgets(self):
> """Return all the mailing list subscription widgets."""
> - return [widget for widget in self.widgets
> - if 'field.subscription.' in widget.name]
> + mailing_list_set = getUtility(IMailingListSet)
> + widgets = []
> + for widget in self.widgets:
> + if 'field.subscription.' in widget.name:

Why not use 'startswith'? Can it really be anywhere in the name?

> + team_name = widget.label
> + mailing_list = mailing_list_set.get(team_name)
> + assert mailing_list is not None, 'Missing mailing list'
> + widget_dict = dict(
> + team=mailing_list.team,
> + widget=widget,
> + )
> + widgets.append(widget_dict)
> + # We'll put the label in the first column, so don't include it
> + # in the second column.
> + widget.display_label = False
> + return widgets
>
> def _validate_selected_address(self, data, field='VALIDATED_SELECTED'):
> """A generic validator for this view's actions.

> === modified file 'lib/lp/registry/stories/foaf/xx-setpreferredemail.txt'
> --- lib/lp/registry/stories/foaf/xx-setpreferredemail.txt 2008-09-08 13:35:16 +0000
> +++ lib/lp/registry/stories/foaf/xx-setpreferredemail.txt 2009-09-16 21:00:11 +0000
> @@ -1,11 +1,14 @@
> -= A person's contact email address =
> +================================
> +A person's contact email address
> +================================

Thanks for converting this test.

> === modified file 'lib/lp/registry/stories/mailinglists/subscriptions.txt'
> --- lib/lp/registry/stories/maili...

Read more...

review: Approve (code)
Revision history for this message
Barry Warsaw (barry) wrote :
Download full text (4.2 KiB)

On Sep 16, 2009, at 10:24 PM, Brad Crittenden wrote:

>The changes look good. Thanks for taking the time to make a lot of drive-by fixes.
>
>I've got a few questions below.

Thanks for the review Brad!

>> === modified file 'lib/lp/registry/browser/person.py'
>> --- lib/lp/registry/browser/person.py 2009-09-16 17:07:14 +0000
>> +++ lib/lp/registry/browser/person.py 2009-09-16 21:19:47 +0000
>> @@ -4086,6 +4086,8 @@
>> custom_widget('mailing_list_auto_subscribe_policy',
>> LaunchpadRadioWidgetWithDescription)
>>
>> + label = 'Change your e-mail settings'
>> +
>> def initialize(self):
>> if self.context.is_team:
>> # +editemails is not available on teams.
>> @@ -4191,13 +4193,11 @@
>> which the user is subscribed to this mailing list.
>> """
>> subscription = mailing_list.getSubscription(self.context)
>> - if subscription is not None:
>> - if subscription.email_address is None:
>> - return "Preferred address"
>> - else:
>> - return subscription.email_address
>> - else:
>> + if subscription is None:
>> return "Don't subscribe"
>> + if subscription.email_address is None:
>
>Hmm, I'd have used an elif.

Changed.

>
>> + return 'Preferred address'
>> + return subscription.email_address
>>
>> def _mailing_list_fields(self):
>> """Creates a field for each mailing list the user can subscribe to.
>> @@ -4233,8 +4235,22 @@
>> @property
>> def mailing_list_widgets(self):
>> """Return all the mailing list subscription widgets."""
>> - return [widget for widget in self.widgets
>> - if 'field.subscription.' in widget.name]
>> + mailing_list_set = getUtility(IMailingListSet)
>> + widgets = []
>> + for widget in self.widgets:
>> + if 'field.subscription.' in widget.name:
>
>Why not use 'startswith'? Can it really be anywhere in the name?

Good call. This was cut-and-pasted from the original code, but .startswith()
is better, so I changed this.

>> @@ -87,24 +77,21 @@
>> lists. You can subscribe to a team's mailing list using any of
>> your verified email addresses.</p>
>>
>> - <table class="form">
>> + <table class="listing" style="width: 45em;">
>> <thead>
>> - <th style="white-space:nowrap">For this mailing list...</th>
>> - <td><strong>Subscribe with this address</strong></td>
>> + <th style="white-space:nowrap">For mailing list</th>
>
>Missing semi-colon

Cut-and-paste, but good catch! Changed. Here's the incremental.

=== modified file 'lib/lp/registry/browser/person.py'
--- lib/lp/registry/browser/person.py 2009-09-16 21:19:47 +0000
+++ lib/lp/registry/browser/person.py 2009-09-16 23:50:16 +0000
@@ -4195,9 +4195,10 @@
         subscription = mailing_list.getSubscription(self.context)
         if subscription is None:
             return "Don't subscribe"
- if subscription.email_address is None:
+ elif subscription.email_address is None:
             return 'Preferred address'
- return subscription.ema...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/browser/launchpad.py'
2--- lib/canonical/launchpad/browser/launchpad.py 2009-09-08 22:42:42 +0000
3+++ lib/canonical/launchpad/browser/launchpad.py 2009-09-16 19:56:45 +0000
4@@ -250,7 +250,7 @@
5 if extra_breadcrumb is not None:
6 breadcrumbs.insert(idx + 1, extra_breadcrumb)
7 break
8- if len(breadcrumbs):
9+ if len(breadcrumbs) > 0:
10 page_crumb = self.makeBreadcrumbForRequestedPage()
11 if page_crumb:
12 breadcrumbs.append(page_crumb)
13
14=== modified file 'lib/canonical/launchpad/pagetitles.py'
15--- lib/canonical/launchpad/pagetitles.py 2009-09-16 17:07:14 +0000
16+++ lib/canonical/launchpad/pagetitles.py 2009-09-16 21:27:18 +0000
17@@ -657,15 +657,8 @@
18 person_answer_contact_for = ContextDisplayName(
19 'Projects for which %s is an answer contact')
20
21-person_changepassword = 'Change your password'
22-
23-person_codesofconduct = ContextDisplayName(
24- smartquote("%s's code of conduct signatures"))
25-
26 person_edit = ContextDisplayName(smartquote("%s's details"))
27
28-person_editemails = ContextDisplayName(smartquote("%s's e-mail addresses"))
29-
30 # person_foaf is an rdf file
31
32 person_hwdb_submissions = ContextDisplayName(
33
34=== modified file 'lib/lp/registry/browser/configure.zcml'
35--- lib/lp/registry/browser/configure.zcml 2009-09-16 17:07:14 +0000
36+++ lib/lp/registry/browser/configure.zcml 2009-09-16 21:19:47 +0000
37@@ -801,7 +801,7 @@
38 for="lp.registry.interfaces.person.IPerson"
39 class="lp.registry.browser.person.PersonChangePasswordView"
40 permission="launchpad.Edit"
41- template="../templates/person-changepassword.pt"/>
42+ template="../../app/templates/generic-edit.pt"/>
43 <browser:page
44 name="+editlanguages"
45 for="lp.registry.interfaces.person.IPerson"
46
47=== modified file 'lib/lp/registry/browser/person.py'
48--- lib/lp/registry/browser/person.py 2009-09-16 17:07:14 +0000
49+++ lib/lp/registry/browser/person.py 2009-09-16 21:19:47 +0000
50@@ -4086,6 +4086,8 @@
51 custom_widget('mailing_list_auto_subscribe_policy',
52 LaunchpadRadioWidgetWithDescription)
53
54+ label = 'Change your e-mail settings'
55+
56 def initialize(self):
57 if self.context.is_team:
58 # +editemails is not available on teams.
59@@ -4191,13 +4193,11 @@
60 which the user is subscribed to this mailing list.
61 """
62 subscription = mailing_list.getSubscription(self.context)
63- if subscription is not None:
64- if subscription.email_address is None:
65- return "Preferred address"
66- else:
67- return subscription.email_address
68- else:
69+ if subscription is None:
70 return "Don't subscribe"
71+ if subscription.email_address is None:
72+ return 'Preferred address'
73+ return subscription.email_address
74
75 def _mailing_list_fields(self):
76 """Creates a field for each mailing list the user can subscribe to.
77@@ -4207,10 +4207,12 @@
78 """
79 mailing_list_set = getUtility(IMailingListSet)
80 fields = []
81- terms = [SimpleTerm("Preferred address"),
82- SimpleTerm("Don't subscribe")]
83- terms += [SimpleTerm(email, email.email)
84- for email in self.validated_addresses]
85+ terms = [
86+ SimpleTerm("Preferred address"),
87+ SimpleTerm("Don't subscribe"),
88+ ]
89+ for email in self.validated_addresses:
90+ terms.append(SimpleTerm(email, email.email))
91 for team in self.context.teams_participated_in:
92 mailing_list = mailing_list_set.get(team.name)
93 if mailing_list is not None and mailing_list.is_usable:
94@@ -4233,8 +4235,22 @@
95 @property
96 def mailing_list_widgets(self):
97 """Return all the mailing list subscription widgets."""
98- return [widget for widget in self.widgets
99- if 'field.subscription.' in widget.name]
100+ mailing_list_set = getUtility(IMailingListSet)
101+ widgets = []
102+ for widget in self.widgets:
103+ if 'field.subscription.' in widget.name:
104+ team_name = widget.label
105+ mailing_list = mailing_list_set.get(team_name)
106+ assert mailing_list is not None, 'Missing mailing list'
107+ widget_dict = dict(
108+ team=mailing_list.team,
109+ widget=widget,
110+ )
111+ widgets.append(widget_dict)
112+ # We'll put the label in the first column, so don't include it
113+ # in the second column.
114+ widget.display_label = False
115+ return widgets
116
117 def _validate_selected_address(self, data, field='VALIDATED_SELECTED'):
118 """A generic validator for this view's actions.
119@@ -4479,7 +4495,8 @@
120 Valid addresses are the ones presented as options for the mailing
121 list widgets.
122 """
123- names = [w.context.getName() for w in self.mailing_list_widgets]
124+ names = [widget_dict['widget'].context.getName()
125+ for widget_dict in self.mailing_list_widgets]
126 self.validate_widgets(data, names)
127 return self.errors
128
129@@ -4490,7 +4507,8 @@
130 mailing_list_set = getUtility(IMailingListSet)
131 dirty = False
132 prefix_length = len('subscription.')
133- for widget in self.mailing_list_widgets:
134+ for widget_dict in self.mailing_list_widgets:
135+ widget = widget_dict['widget']
136 mailing_list_name = widget.context.getName()[prefix_length:]
137 mailing_list = mailing_list_set.get(mailing_list_name)
138 new_value = data[widget.context.getName()]
139
140=== modified file 'lib/lp/registry/stories/foaf/xx-setpreferredemail.txt'
141--- lib/lp/registry/stories/foaf/xx-setpreferredemail.txt 2008-09-08 13:35:16 +0000
142+++ lib/lp/registry/stories/foaf/xx-setpreferredemail.txt 2009-09-16 21:00:11 +0000
143@@ -1,11 +1,14 @@
144-= A person's contact email address =
145+================================
146+A person's contact email address
147+================================
148
149 A person may have many confirmed email address which he may use to
150 login with, but only one email address will be the contact email
151 address.
152
153
154-== Setting the contact email address ==
155+Setting the contact email address
156+=================================
157
158 Sample Person chooses to change his contact email address to the
159 address he prefers to login with.
160@@ -14,10 +17,10 @@
161 >>> browser.open('http://launchpad.dev/~name12')
162 >>> browser.getLink('Change details').click()
163 >>> browser.getLink('E-mail Settings').click()
164- >>> browser.url
165- 'http://launchpad.dev/~name12/+editemails'
166- >>> browser.title
167- "Sample Person's e-mail addresses"
168+ >>> print browser.url
169+ http://launchpad.dev/~name12/+editemails
170+ >>> print browser.title
171+ +editemails : Sample Person
172
173 Sample Person has a second browser open to the same page; maybe he is
174 absent minded.
175@@ -62,13 +65,14 @@
176 testing@canonical.com is already set as your contact address.
177
178
179-== Unclaimed users ==
180+Unclaimed users
181+===============
182
183 An unclaimed user will probably have no contact email address.
184
185 >>> admin_browser.open('http://launchpad.dev/~matsubara')
186- >>> admin_browser.title
187- 'Diogo Matsubara does not use Launchpad'
188+ >>> print admin_browser.title
189+ Diogo Matsubara does not use Launchpad
190 >>> admin_browser.getLink('Change e-mail settings').click()
191 >>> print find_tag_by_id(admin_browser.contents,
192 ... 'no-contact-address').string
193
194=== modified file 'lib/lp/registry/stories/foaf/xx-validate-email.txt'
195--- lib/lp/registry/stories/foaf/xx-validate-email.txt 2009-09-10 23:58:31 +0000
196+++ lib/lp/registry/stories/foaf/xx-validate-email.txt 2009-09-16 21:00:11 +0000
197@@ -1,4 +1,6 @@
198-= Validating an email address =
199+===========================
200+Validating an email address
201+===========================
202
203 The user 'salgado' has an unvalidated email address that was probably
204 added by gina. Now he wants to validate it.
205@@ -10,7 +12,7 @@
206 >>> browser = setupBrowser(auth='Basic salgado@ubuntu.com:zeca')
207 >>> browser.open('http://launchpad.dev/~salgado/+editemails')
208 >>> print browser.title
209- Guilherme Salgado's e-mail addresses
210+ +editemails : Guilherme Salgado
211
212 >>> browser.getControl(name="field.UNVALIDATED_SELECTED").getControl(
213 ... value='salgado@ubuntu.com').selected = True
214@@ -79,7 +81,8 @@
215 An e-mail message was sent to 'salgado@example.com'...
216
217
218-== Validating the email address string ==
219+Validating the email address string
220+===================================
221
222 Leaving the email address field blank and hitting the 'Add' button
223 should display an error message.
224@@ -88,8 +91,8 @@
225 ''
226
227 >>> browser.getControl('Add', index=1).click()
228- >>> browser.title
229- "Guilherme Salgado's e-mail addresses"
230+ >>> print browser.title
231+ +editemails : Guilherme Salgado
232
233 >>> for msg in get_feedback_messages(browser.contents):
234 ... print msg
235@@ -99,13 +102,13 @@
236 Entering a string that does not look like an email address causes an
237 error to be displayed.
238
239- >>> browser.title
240- "Guilherme Salgado's e-mail addresses"
241+ >>> print browser.title
242+ +editemails : Guilherme Salgado
243
244 >>> browser.getControl('Add a new address').value = 'foo'
245 >>> browser.getControl('Add', index=1).click()
246- >>> browser.title
247- "Guilherme Salgado's e-mail addresses"
248+ >>> print browser.title
249+ +editemails : Guilherme Salgado
250
251 >>> for msg in get_feedback_messages(browser.contents):
252 ... print msg
253@@ -117,8 +120,8 @@
254 will not run. A malicious hacker cannot use a XSS vulnerability to gain
255 information about Launchpad users.
256
257- >>> browser.title
258- "Guilherme Salgado's e-mail addresses"
259+ >>> print browser.title
260+ +editemails : Guilherme Salgado
261
262 >>> browser.getControl('Add a new address').value = (
263 ... "salgado@example.com<br/><script>window.alert('XSS')</script>")
264@@ -130,7 +133,8 @@
265 doesn't seem to be a valid email address.
266
267
268-== +editemails for teams ==
269++editemails for teams
270+=====================
271
272 Because people and teams share the same namespace, it used to be possible to
273 hack the url to get to a team's +editemails page. This makes no sense, and
274
275=== modified file 'lib/lp/registry/stories/mailinglists/subscriptions.txt'
276--- lib/lp/registry/stories/mailinglists/subscriptions.txt 2009-09-14 01:28:41 +0000
277+++ lib/lp/registry/stories/mailinglists/subscriptions.txt 2009-09-16 21:00:11 +0000
278@@ -85,8 +85,10 @@
279 >>> browser.open('http://launchpad.dev/~carlos')
280 >>> browser.getLink("Change details").click()
281 >>> browser.getLink("E-mail Settings").click()
282- >>> browser.title
283- "Carlos Perell\xc3\xb3 Mar\xc3\xadn's e-mail addresses"
284+
285+ >>> from canonical.launchpad.helpers import backslashreplace
286+ >>> print backslashreplace(browser.title)
287+ +editemails : Carlos Perell\xf3 Mar\xedn
288
289 >>> admins = browser.getControl(name='field.subscription.admins')
290 >>> rosetta_admins = browser.getControl(
291@@ -211,8 +213,8 @@
292 >>> browser.open('http://launchpad.dev/~carlos')
293 >>> browser.getLink("Change details").click()
294 >>> browser.getLink("E-mail Settings").click()
295- >>> browser.title
296- "Carlos Perell\xc3\xb3 Mar\xc3\xadn's e-mail addresses"
297+ >>> print backslashreplace(browser.title)
298+ +editemails : Carlos Perell\xf3 Mar\xedn
299
300 >>> rosetta_admins = browser.getControl(
301 ... name='field.subscription.rosetta-admins')
302@@ -245,8 +247,8 @@
303 >>> browser.open('http://launchpad.dev/~jdub')
304 >>> browser.getLink("Change details").click()
305 >>> browser.getLink("E-mail Settings").click()
306- >>> browser.title
307- "Jeff Waugh's e-mail addresses"
308+ >>> print browser.title
309+ +editemails : Jeff Waugh
310
311 >>> browser.getControl(
312 ... name='field.subscription.rosetta-admins')
313@@ -270,8 +272,8 @@
314 >>> browser.open('http://launchpad.dev/~jdub')
315 >>> browser.getLink("Change details").click()
316 >>> browser.getLink("E-mail Settings").click()
317- >>> browser.title
318- "Jeff Waugh's e-mail addresses"
319+ >>> print browser.title
320+ +editemails : Jeff Waugh
321
322 >>> rosetta_team = browser.getControl(
323 ... name='field.subscription.rosetta-admins')
324@@ -472,8 +474,8 @@
325 >>> browser.open('http://launchpad.dev/~carlos')
326 >>> browser.getLink("Change details").click()
327 >>> browser.getLink("E-mail Settings").click()
328- >>> browser.title
329- "Carlos Perell\xc3\xb3 Mar\xc3\xadn's e-mail addresses"
330+ >>> print backslashreplace(browser.title)
331+ +editemails : Carlos Perell\xf3 Mar\xedn
332
333 Carlos's default setting, 'Ask me when I join a team', is still in place.
334
335
336=== removed file 'lib/lp/registry/templates/person-changepassword.pt'
337--- lib/lp/registry/templates/person-changepassword.pt 2009-07-18 00:05:49 +0000
338+++ lib/lp/registry/templates/person-changepassword.pt 1970-01-01 00:00:00 +0000
339@@ -1,32 +0,0 @@
340-<html
341- xmlns="http://www.w3.org/1999/xhtml"
342- xmlns:tal="http://xml.zope.org/namespaces/tal"
343- xmlns:metal="http://xml.zope.org/namespaces/metal"
344- xmlns:i18n="http://xml.zope.org/namespaces/i18n"
345- xml:lang="en"
346- lang="en"
347- dir="ltr"
348- metal:use-macro="view/macro:page/onecolumn"
349- i18n:domain="launchpad"
350->
351- <body>
352-
353- <metal:style fill-slot="head_epilogue">
354- <style type="text/css">
355- #launchpad-form-widgets th {
356- width: 12em;
357- }
358- </style>
359- </metal:style>
360-
361-
362- <div metal:fill-slot="main">
363-
364- <div metal:use-macro="context/@@launchpad_form/form">
365-
366- </div>
367-
368- </div>
369-
370-</body>
371-</html>
372
373=== modified file 'lib/lp/registry/templates/person-editemails.pt'
374--- lib/lp/registry/templates/person-editemails.pt 2009-07-17 17:59:07 +0000
375+++ lib/lp/registry/templates/person-editemails.pt 2009-09-16 21:00:11 +0000
376@@ -3,35 +3,25 @@
377 xmlns:tal="http://xml.zope.org/namespaces/tal"
378 xmlns:metal="http://xml.zope.org/namespaces/metal"
379 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
380- xml:lang="en"
381- lang="en"
382- dir="ltr"
383- metal:use-macro="view/macro:page/onecolumn"
384+ metal:use-macro="view/macro:page/main_only"
385 i18n:domain="launchpad"
386 >
387 <body>
388 <div metal:fill-slot="main">
389 <div metal:use-macro="context/@@launchpad_form/form">
390 <metal:extra-info fill-slot="extra_info">
391- <h1>Change your e-mail settings</h1>
392 <h2>Your e-mail addresses</h2>
393- <tal:block condition="context/preferredemail">
394- <p>Your preferred contact address for all Launchpad e-mail is:</p>
395- <ul id="preferred-email">
396- <li class="mail"><b tal:content="context/preferredemail/email" /></li>
397- </ul>
398- </tal:block>
399-
400- <tal:block tal:condition="not: context/preferredemail">
401- <p id="no-contact-address">
402- Currently you don't have a contact address in
403- Launchpad.
404- </p>
405- </tal:block>
406+ <p tal:condition="context/preferredemail">
407+ Your preferred contact address for all Launchpad e-mail is:
408+ <b tal:content="context/preferredemail/email" />
409+ </p>
410+ <p tal:condition="not: context/preferredemail"
411+ id="no-contact-address">
412+ Currently you don't have a contact address in Launchpad.
413+ </p>
414 </metal:extra-info>
415
416 <metal:widgets fill-slot="widgets">
417-
418 <table class="form">
419 <tal:validated tal:condition="view/validated_addresses">
420 <tal:widget define="widget nocall:view/widgets/VALIDATED_SELECTED">
421@@ -87,24 +77,21 @@
422 lists. You can subscribe to a team's mailing list using any of
423 your verified email addresses.</p>
424
425- <table class="form">
426+ <table class="listing" style="width: 45em;">
427 <thead>
428- <th style="white-space:nowrap">For this mailing list...</th>
429- <td><strong>Subscribe with this address</strong></td>
430+ <th style="white-space:nowrap">For mailing list</th>
431+ <th>Subscribe with</th>
432 </thead>
433 <tbody>
434- <metal:block tal:repeat="widget view/mailing_list_widgets">
435- <metal:block use-macro="context/@@launchpad_form/widget_row" />
436- </metal:block>
437- <tr>
438- <td></td>
439- <td>
440- <input tal:replace="structure
441- view/action_update_subscriptions/render" />
442- </td>
443- </tr>
444+ <tr tal:repeat="widget view/mailing_list_widgets">
445+ <td tal:content="structure widget/team/fmt:link">Team</td>
446+ <td tal:content="structure widget/widget">Widget</td>
447+ </tr>
448 </tbody>
449 </table>
450+ <div style="padding-top: 1em; padding-bottom: 1em;">
451+ <input tal:replace="structure view/action_update_subscriptions/render" />
452+ </div>
453 </form>
454
455 <form action=""
456@@ -128,7 +115,7 @@
457 <th style="white-space:nowrap; text-align:left;">
458 <div style="margin-bottom: 1em;">
459 <label tal:attributes="for widget/name"
460- tal:content="structure widget/label" />
461+ tal:content="structure widget/label" />
462 </div>
463 </th>
464 <td></td>
465@@ -140,8 +127,8 @@
466 <tr>
467 <td>
468 <input
469- tal:replace="structure
470- view/action_update_autosubscribe_policy/render" />
471+ tal:replace="structure
472+ view/action_update_autosubscribe_policy/render" />
473 </td>
474 <td></td>
475 </tr>
476@@ -152,37 +139,5 @@
477 </form>
478
479 </div>
480-
481-<div metal:fill-slot="help">
482- <p>
483- This page displays all the email addresses associated with your
484- Launchpad account. Some of these addresses are verified (known to
485- be associated with you), but some may need confirmation before you
486- can use them in Launchpad.
487- </p>
488-
489- <p>
490- When you confirm that an address is yours, Launchpad will send a
491- verification email to that address. To complete the verification
492- process, simply visit the URL contained in the email.
493- </p>
494-
495- <p>
496- You may choose one of your verified addresses to be your
497- preferred email address. Email from Launchpad will be sent to
498- this address.
499- </p>
500-
501- <p id="mailing-list-help" tal:condition="view/mailing_list_widgets">
502- This page also displays mailing lists for all of your
503- teams that have them. You can subscribe to a list using any of
504- your verified email addresses, or you can choose to have messages
505- delivered to your preferred address. That way, if you change your
506- preferred address, you won't have to change all your subscriptions
507- to use the new address.
508- </p>
509-
510-</div>
511-
512 </body>
513 </html>