Merge lp:~bac/launchpad/bug-32618 into lp:launchpad

Proposed by Brad Crittenden on 2010-08-09
Status: Merged
Approved by: Curtis Hovey on 2010-08-09
Approved revision: no longer in the source branch.
Merged at revision: 11331
Proposed branch: lp:~bac/launchpad/bug-32618
Merge into: lp:launchpad
Diff against target: 320 lines (+91/-90)
5 files modified
lib/canonical/launchpad/utilities/gpghandler.py (+6/-6)
lib/lp/registry/browser/person.py (+42/-31)
lib/lp/registry/interfaces/jabber.py (+1/-2)
lib/lp/registry/stories/person/xx-person-edit-jabber-ids.txt (+29/-29)
lib/lp/registry/templates/person-editjabberids.pt (+13/-22)
To merge this branch: bzr merge lp:~bac/launchpad/bug-32618
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code 2010-08-09 Approve on 2010-08-09
Review via email: mp+32145@code.launchpad.net

Commit Message

Do not allow editing of jabberids in place. Convert to be a real LaunchpadFormView.

Description of the Change

= Summary =

Multiple Jabberids can be edited in place with confusing results. The
UI is confusing. Simplifying the UI makes the problem impossible to
recreate.

== Proposed fix ==

Only display the jabberids, don't show them in editable fields. Need to
change a jabberid? Delete it and add it back correctly.

== Pre-implementation notes ==

Chit chat with Curtis.

== Implementation details ==

None

== Tests ==

bin/test -vvt xx-person-edit-jabber-ids.txt

== Demo and Q/A ==

http://launchpad.dev/~mark/+editjabberids

= Launchpad lint =

Will fix these.

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/registry/stories/person/xx-person-edit-jabber-ids.txt
  lib/lp/registry/templates/person-editjabberids.pt
  lib/lp/registry/browser/person.py

./lib/lp/registry/stories/person/xx-person-edit-jabber-ids.txt
       0: narrative uses a moin header.
       4: narrative uses a moin header.
      32: source exceeds 78 characters.
      45: narrative uses a moin header.

To post a comment you must log in.
Curtis Hovey (sinzui) wrote :

Hi Brad.

I love your proposed soltution that made this issue a trival problem. I see your editor converted a tab into a spaces :).

I have an optional request. I see that the save() method is doing validation /after/ it starts making changes to the jabber ids. I think we should move the validation code to a validate mthod and make the save() method only do changes. I see there are already tests showing the expected behaviour, and I expect this proposed refactoring to pass:

    def validate(self, data):
        jabberid = data.get('newjabberid')
        if jabberid is not None:
            jabberset = getUtility(IJabberIDSet)
            existingjabber = jabberset.getByJabberID(jabberid)
            if existingjabber.person != self.context:
                self.request.response.addErrorNotification(
                    structured(
                        'The Jabber ID %s is already registered by '
                        '<a href="%s">%s</a>.',
                        jabberid, canonical_url(existingjabber.person),
                        existingjabber.person.displayname))
            else:
                self.request.response.addErrorNotification(
                    'The Jabber ID %s already belongs to you.' % jabberid)
...
    @action(_("Save Changes"), name="save")
    def save(self, action, data):
...
        jabberid = form.get('newjabberid')
        if jabberid is not None:
            jabberset.new(self.context, jabberid)

review: Approve (code)
Brad Crittenden (bac) wrote :

Thanks for the comments, Curtis. I converted the form to be a proper LaunchpadFormView with the validation method. This also fixed bug 615872.

Incremental at http://pastebin.ubuntu.com/475950/

Curtis Hovey (sinzui) wrote :

I did not mean to cause any consternation. I really appreciate your fix to this old code and that this also addresses another bug.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/utilities/gpghandler.py'
2--- lib/canonical/launchpad/utilities/gpghandler.py 2010-05-15 17:43:59 +0000
3+++ lib/canonical/launchpad/utilities/gpghandler.py 2010-08-10 16:19:43 +0000
4@@ -163,10 +163,10 @@
5 # XXX: 2010-04-26, Salgado, bug=570244: This hack is needed
6 # for python2.5 compatibility. We should remove it when we no
7 # longer need to run on python2.5.
8- if hasattr(e, 'message'):
9+ if hasattr(e, 'strerror'):
10+ msg = e.strerror
11+ else:
12 msg = e.message
13- else:
14- msg = e.strerror
15 raise GPGVerificationError(msg)
16 else:
17 # store clearsigned signature
18@@ -180,10 +180,10 @@
19 # XXX: 2010-04-26, Salgado, bug=570244: This hack is needed
20 # for python2.5 compatibility. We should remove it when we no
21 # longer need to run on python2.5.
22- if hasattr(e, 'message'):
23+ if hasattr(e, 'strerror'):
24+ msg = e.strerror
25+ else:
26 msg = e.message
27- else:
28- msg = e.strerror
29 raise GPGVerificationError(msg)
30
31 # XXX jamesh 2006-01-31:
32
33=== modified file 'lib/lp/registry/browser/person.py'
34--- lib/lp/registry/browser/person.py 2010-08-05 22:12:44 +0000
35+++ lib/lp/registry/browser/person.py 2010-08-10 16:19:43 +0000
36@@ -1,4 +1,4 @@
37-# Copyright 2009 Canonical Ltd. This software is licensed under the
38+# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
39 # GNU Affero General Public License version 3 (see the file LICENSE).
40
41 # pylint: disable-msg=E0211,E0213,C0322
42@@ -156,7 +156,7 @@
43 from lp.registry.interfaces.codeofconduct import ISignedCodeOfConductSet
44 from lp.registry.interfaces.gpg import IGPGKeySet
45 from lp.registry.interfaces.irc import IIrcIDSet
46-from lp.registry.interfaces.jabber import IJabberIDSet
47+from lp.registry.interfaces.jabber import IJabberID, IJabberIDSet
48 from lp.registry.interfaces.mailinglist import (
49 CannotUnsubscribe, IMailingListSet)
50 from lp.registry.interfaces.mailinglistsubscription import (
51@@ -3520,7 +3520,18 @@
52
53
54 class PersonEditJabberIDsView(LaunchpadFormView):
55- schema = Interface
56+
57+ schema = IJabberID
58+ field_names = ['jabberid']
59+
60+ def setUpFields(self):
61+ super(PersonEditJabberIDsView, self).setUpFields()
62+ if self.context.jabberids.count() > 0:
63+ # Make the jabberid entry optional on the edit page if one or more
64+ # ids already exist, which allows the removal of ids without
65+ # filling out the new jabberid field.
66+ jabber_field = self.form_fields['jabberid']
67+ jabber_field.field.required = False
68
69 @property
70 def page_title(self):
71@@ -3529,42 +3540,42 @@
72 label = page_title
73
74 @property
75- def cancel_url(self):
76+ def next_url(self):
77 return canonical_url(self.context)
78
79+ cancel_url = next_url
80+
81+ def validate(self, data):
82+ """Ensure the edited data does not already exist."""
83+ jabberid = data.get('jabberid')
84+ if jabberid is not None:
85+ jabberset = getUtility(IJabberIDSet)
86+ existingjabber = jabberset.getByJabberID(jabberid)
87+ if existingjabber is not None:
88+ if existingjabber.person != self.context:
89+ self.setFieldError(
90+ 'jabberid',
91+ structured(
92+ 'The Jabber ID %s is already registered by '
93+ '<a href="%s">%s</a>.',
94+ jabberid, canonical_url(existingjabber.person),
95+ existingjabber.person.displayname))
96+ else:
97+ self.setFieldError(
98+ 'jabberid',
99+ 'The Jabber ID %s already belongs to you.' % jabberid)
100+
101 @action(_("Save Changes"), name="save")
102 def save(self, action, data):
103 """Process the Jabber ID form."""
104- # XXX: EdwinGrubbs 2009-09-01 bug=422784
105- # This view should use schema and form validation.
106 form = self.request.form
107 for jabber in self.context.jabberids:
108 if form.get('remove_%s' % jabber.jabberid):
109 jabber.destroySelf()
110- else:
111- jabberid = form.get('jabberid_%s' % jabber.jabberid)
112- if not jabberid:
113- self.request.response.addErrorNotification(
114- "You cannot save an empty Jabber ID.")
115- return
116- jabber.jabberid = jabberid
117-
118- jabberid = form.get('newjabberid')
119- if jabberid:
120+ jabberid = data.get('jabberid')
121+ if jabberid is not None:
122 jabberset = getUtility(IJabberIDSet)
123- existingjabber = jabberset.getByJabberID(jabberid)
124- if existingjabber is None:
125- jabberset.new(self.context, jabberid)
126- elif existingjabber.person != self.context:
127- self.request.response.addErrorNotification(
128- structured(
129- 'The Jabber ID %s is already registered by '
130- '<a href="%s">%s</a>.',
131- jabberid, canonical_url(existingjabber.person),
132- existingjabber.person.displayname))
133- else:
134- self.request.response.addErrorNotification(
135- 'The Jabber ID %s already belongs to you.' % jabberid)
136+ jabberset.new(self.context, jabberid)
137
138
139 class PersonEditSSHKeysView(LaunchpadView):
140@@ -3600,8 +3611,8 @@
141
142 def add_ssh(self):
143 sshkey = self.request.form.get('sshkey')
144- try:
145- getUtility(ISSHKeySet).new(self.user, sshkey)
146+ try:
147+ getUtility(ISSHKeySet).new(self.user, sshkey)
148 except SSHKeyAdditionError:
149 self.error_message = structured('Invalid public key')
150 except SSHKeyCompromisedError:
151
152=== modified file 'lib/lp/registry/interfaces/jabber.py'
153--- lib/lp/registry/interfaces/jabber.py 2009-07-17 00:26:05 +0000
154+++ lib/lp/registry/interfaces/jabber.py 2010-08-10 16:19:43 +0000
155@@ -33,7 +33,7 @@
156 Reference(
157 title=_("Owner"), required=True, schema=Interface, readonly=True))
158 jabberid = exported(
159- TextLine(title=_("Jabber user ID"), required=True))
160+ TextLine(title=_("New Jabber user ID"), required=True))
161
162 def destroySelf():
163 """Delete this JabberID from the database."""
164@@ -50,4 +50,3 @@
165
166 def getByPerson(person):
167 """Return all JabberIDs for the given person."""
168-
169
170=== modified file 'lib/lp/registry/stories/person/xx-person-edit-jabber-ids.txt'
171--- lib/lp/registry/stories/person/xx-person-edit-jabber-ids.txt 2009-11-07 04:30:07 +0000
172+++ lib/lp/registry/stories/person/xx-person-edit-jabber-ids.txt 2010-08-10 16:19:43 +0000
173@@ -1,8 +1,12 @@
174-= Jabber IDs =
175+==========
176+Jabber IDs
177+==========
178+
179
180 In their Launchpad profile, users can register their Jabber IDs.
181
182-== Adding and editing an ID ==
183+Adding and editing an ID
184+------------------------
185
186 To register a Jabber ID with his account, the user visits his
187 profile page and uses the 'Update Jabber IDs' link.
188@@ -15,50 +19,46 @@
189 The user enters the Jabber ID in the text field and clicks on the
190 'Save Changes' button.
191
192- >>> user_browser.getControl(name='newjabberid').value = 'jeff@jabber.org'
193+ >>> user_browser.getControl(name='field.jabberid').value = (
194+ ... 'jeff@jabber.org')
195 >>> user_browser.getControl('Save Changes').click()
196
197 In this case, the user tried registering a jabber ID that was already
198 registered by someone else. Since only one person can use a Jabber ID,
199 an error is displayed and the user can enter another one:
200
201- >>> for error in find_tags_by_class(user_browser.contents, 'error'):
202- ... print extract_text(error)
203+ >>> def show_errors(browser):
204+ ... for error in find_tags_by_class(browser.contents, 'error'):
205+ ... print extract_text(error)
206+ >>> show_errors(user_browser)
207+ There is 1 error.
208+ New Jabber user ID:
209 The Jabber ID jeff@jabber.org is already registered by Jeff Waugh.
210
211 However, if the user enters a Jabber ID which isn't already registered,
212 it will be associated with his account.
213
214- >>> user_browser.getControl(name='newjabberid').value = 'nopriv@jabber.org'
215- >>> user_browser.getControl('Save Changes').click()
216-
217-The new ID can be edited by changing its value, and another ID can
218-also be entered.
219-
220- >>> user_browser.getControl(name='jabberid_nopriv@jabber.org').value = (
221+ >>> user_browser.getControl(name='field.jabberid').value = (
222 ... 'no-priv@jabber.org')
223- >>> user_browser.getControl(name='newjabberid').value = (
224- ... 'no-priv@gmail.org')
225 >>> user_browser.getControl('Save Changes').click()
226-
227- >>> import re
228- >>> main_content = find_main_content(user_browser.contents)
229- >>> for input in main_content.findAll(
230- ... 'input', {'name': re.compile('jabberid_')}):
231- ... print input['value']
232- no-priv@gmail.org
233+ >>> show_errors(user_browser)
234+
235+ >>> def show_jabberids(browser):
236+ ... tags = find_tag_by_id(browser.contents, 'jabber-ids')
237+ ... for dd in tags.findAll('dd'):
238+ ... print extract_text(dd)
239+
240+ >>> show_jabberids(user_browser)
241 no-priv@jabber.org
242
243-== Removing an ID ==
244+Removing an ID
245+--------------
246
247-To remove an existing Jabber ID, the user simply check the 'Remove'
248+To remove an existing Jabber ID, the user simply checks the 'Remove'
249 checkbox besides the ID:
250-
251+ >>> user_browser.getLink('Update Jabber IDs').click()
252 >>> user_browser.getControl('Remove', index=0).click()
253 >>> user_browser.getControl('Save Changes').click()
254
255- >>> main_content = find_main_content(user_browser.contents)
256- >>> for input in main_content.findAll(
257- ... 'input', {'name': re.compile('jabberid_')}):
258- ... print input['value']
259- no-priv@jabber.org
260+ >>> show_jabberids(user_browser)
261+ No Jabber IDs registered.
262
263=== modified file 'lib/lp/registry/templates/person-editjabberids.pt'
264--- lib/lp/registry/templates/person-editjabberids.pt 2009-09-01 19:30:03 +0000
265+++ lib/lp/registry/templates/person-editjabberids.pt 2010-08-10 16:19:43 +0000
266@@ -13,17 +13,15 @@
267
268 <table>
269
270- <div tal:condition="context/jabberids">
271+ <tal:existing_jabber condition="context/jabberids">
272
273 <tr>
274 <td><label>Existing Jabber IDs:</label></td>
275 </tr>
276
277 <tr tal:repeat="jabber context/jabberids">
278- <td>
279- <input tal:attributes="name string:jabberid_${jabber/jabberid};
280- value jabber/jabberid"
281- type="text" style="margin-bottom: 0.5em;"/>
282+ <td tal:content="jabber/jabberid"
283+ class="jabberid">
284 </td>
285
286 <td>
287@@ -34,23 +32,16 @@
288 </label>
289 </td>
290 </tr>
291-
292- </div>
293-
294- <tr tal:condition="context/jabberids">
295- <td><label>New Jabber ID:</label></td>
296- </tr>
297-
298- <tr>
299- <td>
300- <input name="newjabberid" type="text" />
301- </td>
302- </tr>
303-
304- <tr>
305- <td class="formHelp">Example: yourname@jabber.org</td>
306- </tr>
307- </table>
308+ </tal:existing_jabber>
309+
310+ <tal:widget define="widget nocall:view/widgets/jabberid">
311+ <metal:block use-macro="context/@@launchpad_form/widget_row" />
312+ </tal:widget>
313+
314+ <tr>
315+ <td class="formHelp">Example: yourname@jabber.org</td>
316+ </tr>
317+ </table>
318
319 </div>
320 </div>