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
=== modified file 'lib/lp/registry/browser/person.py'
--- lib/lp/registry/browser/person.py 2010-09-11 19:25:13 +0000
+++ lib/lp/registry/browser/person.py 2010-09-16 13:43:45 +0000
@@ -4835,6 +4835,11 @@
4835 "'%s' doesn't seem to be a valid email address." % newemail)4835 "'%s' doesn't seem to be a valid email address." % newemail)
4836 return self.errors4836 return self.errors
48374837
4838 # XXX j.c.sackett 2010-09-15 bug=628247, 576757 There is a validation
4839 # system set up for this that is almost identical in
4840 # canonical.launchpad.interfaces.validation, called
4841 # _check_email_available or similar. It would be really nice if we
4842 # could merge that code somehow with this.
4838 email = getUtility(IEmailAddressSet).getByEmail(newemail)4843 email = getUtility(IEmailAddressSet).getByEmail(newemail)
4839 person = self.context4844 person = self.context
4840 if email is not None:4845 if email is not None:
@@ -4846,20 +4851,32 @@
4846 "detected it as being yours. If it was detected by our "4851 "detected it as being yours. If it was detected by our "
4847 "system, it's probably shown on this page and is waiting "4852 "system, it's probably shown on this page and is waiting "
4848 "to be confirmed as yours." % newemail)4853 "to be confirmed as yours." % newemail)
4849 else:4854 elif email.person is not None:
4850 owner = email.person4855 owner = email.person
4851 owner_name = urllib.quote(owner.name)4856 owner_name = urllib.quote(owner.name)
4852 merge_url = (4857 merge_url = (
4853 '%s/+requestmerge?field.dupe_person=%s'4858 '%s/+requestmerge?field.dupe_person=%s'
4854 % (canonical_url(getUtility(IPersonSet)), owner_name))4859 % (canonical_url(getUtility(IPersonSet)), owner_name))
4855 self.addError(4860 self.addError(structured(
4856 structured(4861 "The email address '%s' is already registered to "
4857 "The email address '%s' is already registered to "4862 '<a href="%s">%s</a>. If you think that is a '
4858 '<a href="%s">%s</a>. If you think that is a '4863 'duplicated account, you can <a href="%s">merge it'
4859 'duplicated account, you can <a href="%s">merge it'4864 "</a> into your account.",
4860 "</a> into your account.",4865 newemail,
4861 newemail, canonical_url(owner), owner.displayname,4866 canonical_url(owner),
4862 merge_url))4867 owner.displayname,
4868 merge_url))
4869 elif email.account is not None:
4870 account = email.account
4871 self.addError(structured(
4872 "The email address '%s' is already registered to an "
4873 "account, %s.",
4874 newemail,
4875 account.displayname))
4876 else:
4877 self.addError(structured(
4878 "The email address '%s' is already registered.",
4879 newemail))
4863 return self.errors4880 return self.errors
48644881
4865 @action(_("Add"), name="add_email", validator=validate_action_add_email)4882 @action(_("Add"), name="add_email", validator=validate_action_add_email)
48664883
=== modified file 'lib/lp/registry/browser/tests/test_person_view.py'
--- lib/lp/registry/browser/tests/test_person_view.py 2010-09-02 19:28:35 +0000
+++ lib/lp/registry/browser/tests/test_person_view.py 2010-09-16 13:43:45 +0000
@@ -11,7 +11,9 @@
11 ANONYMOUS,11 ANONYMOUS,
12 login,12 login,
13 )13 )
14from canonical.launchpad.interfaces.authtoken import LoginTokenType
14from canonical.launchpad.interfaces.account import AccountStatus15from canonical.launchpad.interfaces.account import AccountStatus
16from canonical.launchpad.interfaces.logintoken import ILoginTokenSet
15from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities17from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
16from canonical.launchpad.webapp.servers import LaunchpadTestRequest18from canonical.launchpad.webapp.servers import LaunchpadTestRequest
17from canonical.testing import (19from canonical.testing import (
@@ -207,7 +209,8 @@
207209
208 def setUp(self):210 def setUp(self):
209 TestCaseWithFactory.setUp(self)211 TestCaseWithFactory.setUp(self)
210 self.person = self.factory.makePerson()212 self.valid_email_address = self.factory.getUniqueEmailAddress()
213 self.person = self.factory.makePerson(email=self.valid_email_address)
211 login_person(self.person)214 login_person(self.person)
212 self.ppa = self.factory.makeArchive(owner=self.person)215 self.ppa = self.factory.makeArchive(owner=self.person)
213 self.view = PersonEditView(216 self.view = PersonEditView(
@@ -256,6 +259,42 @@
256 self.view.initialize()259 self.view.initialize()
257 self.assertFalse(self.view.form_fields['name'].for_display)260 self.assertFalse(self.view.form_fields['name'].for_display)
258261
262 def test_add_email_good_data(self):
263 email_address = self.factory.getUniqueEmailAddress()
264 form = {
265 'field.VALIDATED_SELECTED': self.valid_email_address,
266 'field.VALIDATED_SELECTED-empty-marker': 1,
267 'field.actions.add_email': 'Add',
268 'field.newemail': email_address,
269 }
270 view = create_initialized_view(self.person, "+editemails", form=form)
271
272 # If everything worked, there should now be a login token to validate
273 # this email address for this user.
274 token = getUtility(ILoginTokenSet).searchByEmailRequesterAndType(
275 email_address,
276 self.person,
277 LoginTokenType.VALIDATEEMAIL)
278 self.assertTrue(token is not None)
279
280 def test_add_email_address_taken(self):
281 email_address = self.factory.getUniqueEmailAddress()
282 account = self.factory.makeAccount(
283 displayname='deadaccount',
284 email=email_address,
285 status=AccountStatus.NOACCOUNT)
286 form = {
287 'field.VALIDATED_SELECTED': self.valid_email_address,
288 'field.VALIDATED_SELECTED-empty-marker': 1,
289 'field.actions.add_email': 'Add',
290 'field.newemail': email_address,
291 }
292 view = create_initialized_view(self.person, "+editemails", form=form)
293 error_msg = view.errors[0]
294 expected_msg = ("The email address '%s' is already registered to an "
295 "account, deadaccount." % email_address)
296 self.assertEqual(expected_msg, error_msg)
297
259298
260class TestPersonParticipationView(TestCaseWithFactory):299class TestPersonParticipationView(TestCaseWithFactory):
261300
@@ -602,6 +641,7 @@
602 self.assertEqual(641 self.assertEqual(
603 'This account is already deactivated.', view.errors[0])642 'This account is already deactivated.', view.errors[0])
604643
644
605class TestTeamInvitationView(TestCaseWithFactory):645class TestTeamInvitationView(TestCaseWithFactory):
606 """Tests for TeamInvitationView."""646 """Tests for TeamInvitationView."""
607647