Merge lp:~jcsackett/launchpad/user-email-existing-account-576757 into lp:launchpad

Proposed by j.c.sackett
Status: Merged
Approved by: Robert Collins
Approved revision: no longer in the source branch.
Merged at revision: 11562
Proposed branch: lp:~jcsackett/launchpad/user-email-existing-account-576757
Merge into: lp:launchpad
Diff against target: 131 lines (+67/-10)
2 files modified
lib/lp/registry/browser/person.py (+26/-9)
lib/lp/registry/browser/tests/test_person_view.py (+41/-1)
To merge this branch: bzr merge lp:~jcsackett/launchpad/user-email-existing-account-576757
Reviewer Review Type Date Requested Status
Robert Collins (community) Approve
Review via email: mp+35575@code.launchpad.net

Commit message

Fixes a bad conditional in email validation that assumes the email must have a person, causing an OOPS. Now the validation checks the email info to create the best possible error message from what's available.

Description of the change

Summary
=======

Fixes a condition which raised an OOPS on adding emails if the email already was associated with an account but not a person.

Proposed Fix
============

Create or improve a checker for email addresses so that the condition can't happen.

Pre-implementation notes
========================

Spoke with Curtis Hovey (sinzui) who proposed that the error wasn't actually where the OOPS was raised, but earlier in checking the availability of the email address entered.

Implementation details
======================

Adds a few conditions to the email validation. Where the previous setup simply assumed the email has a person to create the error message, the function now checks that the person actually exists, and if not checks for account to create the message. If no account exists, it defaults to a very basic error message.

Tests
=====

bin/test -t test_add_email

Demo and Q/A
============

If you create an Account without a Person in the database for launchpad.dev, then attempt to add that email to an existing account in the launchpad.dev, it should provide an error message rather than OOPSing.

Lint
====
= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/registry/browser/person.py
  lib/lp/registry/browser/tests/test_person_view.py

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

There's a fair amount of duplicate code in this, mainly in tests. It would be nice to look at reducing that in future.

review: Approve
Revision history for this message
j.c.sackett (jcsackett) wrote :

Agreed. Curtis and I had a long talk about how we might tackle this at some point.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/browser/person.py'
2--- lib/lp/registry/browser/person.py 2010-09-11 19:25:13 +0000
3+++ lib/lp/registry/browser/person.py 2010-09-16 13:43:45 +0000
4@@ -4835,6 +4835,11 @@
5 "'%s' doesn't seem to be a valid email address." % newemail)
6 return self.errors
7
8+ # XXX j.c.sackett 2010-09-15 bug=628247, 576757 There is a validation
9+ # system set up for this that is almost identical in
10+ # canonical.launchpad.interfaces.validation, called
11+ # _check_email_available or similar. It would be really nice if we
12+ # could merge that code somehow with this.
13 email = getUtility(IEmailAddressSet).getByEmail(newemail)
14 person = self.context
15 if email is not None:
16@@ -4846,20 +4851,32 @@
17 "detected it as being yours. If it was detected by our "
18 "system, it's probably shown on this page and is waiting "
19 "to be confirmed as yours." % newemail)
20- else:
21+ elif email.person is not None:
22 owner = email.person
23 owner_name = urllib.quote(owner.name)
24 merge_url = (
25 '%s/+requestmerge?field.dupe_person=%s'
26 % (canonical_url(getUtility(IPersonSet)), owner_name))
27- self.addError(
28- structured(
29- "The email address '%s' is already registered to "
30- '<a href="%s">%s</a>. If you think that is a '
31- 'duplicated account, you can <a href="%s">merge it'
32- "</a> into your account.",
33- newemail, canonical_url(owner), owner.displayname,
34- merge_url))
35+ self.addError(structured(
36+ "The email address '%s' is already registered to "
37+ '<a href="%s">%s</a>. If you think that is a '
38+ 'duplicated account, you can <a href="%s">merge it'
39+ "</a> into your account.",
40+ newemail,
41+ canonical_url(owner),
42+ owner.displayname,
43+ merge_url))
44+ elif email.account is not None:
45+ account = email.account
46+ self.addError(structured(
47+ "The email address '%s' is already registered to an "
48+ "account, %s.",
49+ newemail,
50+ account.displayname))
51+ else:
52+ self.addError(structured(
53+ "The email address '%s' is already registered.",
54+ newemail))
55 return self.errors
56
57 @action(_("Add"), name="add_email", validator=validate_action_add_email)
58
59=== modified file 'lib/lp/registry/browser/tests/test_person_view.py'
60--- lib/lp/registry/browser/tests/test_person_view.py 2010-09-02 19:28:35 +0000
61+++ lib/lp/registry/browser/tests/test_person_view.py 2010-09-16 13:43:45 +0000
62@@ -11,7 +11,9 @@
63 ANONYMOUS,
64 login,
65 )
66+from canonical.launchpad.interfaces.authtoken import LoginTokenType
67 from canonical.launchpad.interfaces.account import AccountStatus
68+from canonical.launchpad.interfaces.logintoken import ILoginTokenSet
69 from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
70 from canonical.launchpad.webapp.servers import LaunchpadTestRequest
71 from canonical.testing import (
72@@ -207,7 +209,8 @@
73
74 def setUp(self):
75 TestCaseWithFactory.setUp(self)
76- self.person = self.factory.makePerson()
77+ self.valid_email_address = self.factory.getUniqueEmailAddress()
78+ self.person = self.factory.makePerson(email=self.valid_email_address)
79 login_person(self.person)
80 self.ppa = self.factory.makeArchive(owner=self.person)
81 self.view = PersonEditView(
82@@ -256,6 +259,42 @@
83 self.view.initialize()
84 self.assertFalse(self.view.form_fields['name'].for_display)
85
86+ def test_add_email_good_data(self):
87+ email_address = self.factory.getUniqueEmailAddress()
88+ form = {
89+ 'field.VALIDATED_SELECTED': self.valid_email_address,
90+ 'field.VALIDATED_SELECTED-empty-marker': 1,
91+ 'field.actions.add_email': 'Add',
92+ 'field.newemail': email_address,
93+ }
94+ view = create_initialized_view(self.person, "+editemails", form=form)
95+
96+ # If everything worked, there should now be a login token to validate
97+ # this email address for this user.
98+ token = getUtility(ILoginTokenSet).searchByEmailRequesterAndType(
99+ email_address,
100+ self.person,
101+ LoginTokenType.VALIDATEEMAIL)
102+ self.assertTrue(token is not None)
103+
104+ def test_add_email_address_taken(self):
105+ email_address = self.factory.getUniqueEmailAddress()
106+ account = self.factory.makeAccount(
107+ displayname='deadaccount',
108+ email=email_address,
109+ status=AccountStatus.NOACCOUNT)
110+ form = {
111+ 'field.VALIDATED_SELECTED': self.valid_email_address,
112+ 'field.VALIDATED_SELECTED-empty-marker': 1,
113+ 'field.actions.add_email': 'Add',
114+ 'field.newemail': email_address,
115+ }
116+ view = create_initialized_view(self.person, "+editemails", form=form)
117+ error_msg = view.errors[0]
118+ expected_msg = ("The email address '%s' is already registered to an "
119+ "account, deadaccount." % email_address)
120+ self.assertEqual(expected_msg, error_msg)
121+
122
123 class TestPersonParticipationView(TestCaseWithFactory):
124
125@@ -602,6 +641,7 @@
126 self.assertEqual(
127 'This account is already deactivated.', view.errors[0])
128
129+
130 class TestTeamInvitationView(TestCaseWithFactory):
131 """Tests for TeamInvitationView."""
132