Merge ~cjwatson/launchpad:stormify-emailaddress into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: b6b3a461db81e8f596f6159b6560ff5d679b781d
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:stormify-emailaddress
Merge into: launchpad:master
Diff against target: 760 lines (+95/-107)
24 files modified
lib/lp/app/doc/batch-navigation.rst (+4/-1)
lib/lp/code/model/revision.py (+1/-1)
lib/lp/registry/browser/peoplemerge.py (+1/-1)
lib/lp/registry/browser/person.py (+2/-2)
lib/lp/registry/doc/person-account.rst (+1/-3)
lib/lp/registry/model/mailinglist.py (+7/-7)
lib/lp/registry/model/person.py (+12/-20)
lib/lp/registry/scripts/closeaccount.py (+2/-2)
lib/lp/registry/stories/teammembership/xx-teammembership.rst (+4/-3)
lib/lp/registry/tests/test_person_merge_job.py (+1/-2)
lib/lp/registry/tests/test_personset.py (+0/-7)
lib/lp/registry/tests/test_team.py (+1/-3)
lib/lp/registry/vocabularies.py (+4/-2)
lib/lp/scripts/garbo.py (+2/-2)
lib/lp/services/database/doc/storm.rst (+1/-1)
lib/lp/services/identity/configure.zcml (+1/-3)
lib/lp/services/identity/interfaces/emailaddress.py (+1/-8)
lib/lp/services/identity/model/emailaddress.py (+26/-23)
lib/lp/services/verification/browser/logintoken.py (+3/-3)
lib/lp/services/webapp/doc/webapp-publication.rst (+1/-1)
lib/lp/soyuz/doc/gina-multiple-arch.rst (+4/-2)
lib/lp/soyuz/doc/gina.rst (+7/-3)
lib/lp/soyuz/model/archivesubscriber.py (+2/-2)
lib/lp/soyuz/tests/test_doc.py (+7/-5)
Reviewer Review Type Date Requested Status
Ines Almeida Approve
Review via email: mp+449757@code.launchpad.net

Commit message

Convert EmailAddress to Storm

To post a comment you must log in.
Revision history for this message
Ines Almeida (ines-almeida) wrote :

LGTM

review: Approve
Revision history for this message
Colin Watson (cjwatson) :
Revision history for this message
Ines Almeida (ines-almeida) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/app/doc/batch-navigation.rst b/lib/lp/app/doc/batch-navigation.rst
2index a93b8b8..25274f1 100644
3--- a/lib/lp/app/doc/batch-navigation.rst
4+++ b/lib/lp/app/doc/batch-navigation.rst
5@@ -64,8 +64,11 @@ Multiple pages
6
7 The batch navigator tells us whether multiple pages will be used.
8
9+ >>> from lp.services.database.interfaces import IStore
10 >>> from lp.services.identity.model.emailaddress import EmailAddress
11- >>> select_results = EmailAddress.select(orderBy="id")
12+ >>> select_results = (
13+ ... IStore(EmailAddress).find(EmailAddress).order_by("id")
14+ ... )
15 >>> batch_nav = BatchNavigator(select_results, build_request(), size=50)
16 >>> batch_nav.has_multiple_pages
17 True
18diff --git a/lib/lp/code/model/revision.py b/lib/lp/code/model/revision.py
19index 3777ebd..b47402d 100644
20--- a/lib/lp/code/model/revision.py
21+++ b/lib/lp/code/model/revision.py
22@@ -235,7 +235,7 @@ class RevisionAuthor(StormBase):
23 return False
24 # Only accept an email address that is validated.
25 if lp_email.status != EmailAddressStatus.NEW:
26- self.person_id = lp_email.personID
27+ self.person_id = lp_email.person_id
28 return True
29 else:
30 return False
31diff --git a/lib/lp/registry/browser/peoplemerge.py b/lib/lp/registry/browser/peoplemerge.py
32index 3dffe4e..b89646b 100644
33--- a/lib/lp/registry/browser/peoplemerge.py
34+++ b/lib/lp/registry/browser/peoplemerge.py
35@@ -166,7 +166,7 @@ class AdminMergeBaseView(ValidatingMergeView):
36 # EmailAddress.person is a readonly field, so we need to
37 # remove the security proxy here.
38 naked_email = removeSecurityProxy(email)
39- naked_email.personID = self.target_person.id
40+ naked_email.person = self.target_person
41 naked_email.status = EmailAddressStatus.NEW
42 getUtility(IPersonSet).mergeAsync(
43 self.dupe_person,
44diff --git a/lib/lp/registry/browser/person.py b/lib/lp/registry/browser/person.py
45index 2ce4be6..5ca39fe 100644
46--- a/lib/lp/registry/browser/person.py
47+++ b/lib/lp/registry/browser/person.py
48@@ -527,7 +527,7 @@ class PersonNavigation(BranchTraversalMixin, Navigation):
49 def traverse_email(self, email):
50 """Traverse to this person's emails on the webservice layer."""
51 email = getUtility(IEmailAddressSet).getByEmail(email)
52- if email is None or email.personID != self.context.id:
53+ if email is None or email.person != self.context:
54 return None
55 return email
56
57@@ -1146,7 +1146,7 @@ class BeginTeamClaimView(LaunchpadFormView):
58 "generated based on the email address it's "
59 "associated with." % self.context.name
60 )
61- elif email.personID != self.context.id:
62+ elif email.person != self.context:
63 error = structured(
64 "This email address is associated with yet another "
65 "Launchpad profile, which you seem to have used at "
66diff --git a/lib/lp/registry/doc/person-account.rst b/lib/lp/registry/doc/person-account.rst
67index e4cf997..21bb628 100644
68--- a/lib/lp/registry/doc/person-account.rst
69+++ b/lib/lp/registry/doc/person-account.rst
70@@ -16,7 +16,6 @@ process. Matsubara's account was created during a code import.
71 >>> from lp.services.identity.interfaces.emailaddress import (
72 ... IEmailAddressSet,
73 ... )
74- >>> from lp.registry.interfaces.person import IPersonSet
75
76 >>> emailset = getUtility(IEmailAddressSet)
77 >>> emailaddress = emailset.getByEmail("matsubara@async.com.br")
78@@ -68,9 +67,8 @@ them, so we'll assign one just to prove that deactivating their account
79 will cause this spec to be reassigned.
80
81
82- >>> personset = getUtility(IPersonSet)
83 >>> foobar_preferredemail = emailset.getByEmail("foo.bar@canonical.com")
84- >>> foobar = personset.get(foobar_preferredemail.personID)
85+ >>> foobar = foobar_preferredemail.person
86 >>> foobar.specifications(None).is_empty()
87 False
88
89diff --git a/lib/lp/registry/model/mailinglist.py b/lib/lp/registry/model/mailinglist.py
90index c27fbae..fd26b09 100644
91--- a/lib/lp/registry/model/mailinglist.py
92+++ b/lib/lp/registry/model/mailinglist.py
93@@ -332,7 +332,7 @@ class MailingList(StormBase):
94 email
95 ).status = EmailAddressStatus.VALIDATED
96 assert (
97- email.personID == self.team_id
98+ email.person == self.team
99 ), "Email already associated with another team."
100
101 def _setAndNotifyDateActivated(self):
102@@ -361,7 +361,7 @@ class MailingList(StormBase):
103 if email is not None and self.team.preferredemail is not None:
104 if email.id == self.team.preferredemail.id:
105 self.team.setContactAddress(None)
106- assert email.personID == self.team_id, "Incorrectly linked email."
107+ assert email.person == self.team, "Incorrectly linked email."
108 # Anyone with permission to deactivate a list can also set the
109 # email address status to NEW.
110 removeSecurityProxy(email).status = EmailAddressStatus.NEW
111@@ -435,7 +435,7 @@ class MailingList(StormBase):
112 raise CannotSubscribe(
113 "Teams cannot be mailing list members: %s" % person.displayname
114 )
115- if address is not None and address.personID != person.id:
116+ if address is not None and address.person != person:
117 raise CannotSubscribe(
118 "%s does not own the email address: %s"
119 % (person.displayname, address.email)
120@@ -469,7 +469,7 @@ class MailingList(StormBase):
121 "%s is not a member of the mailing list: %s"
122 % (person.displayname, self.team.displayname)
123 )
124- if address is not None and address.personID != person.id:
125+ if address is not None and address.person != person:
126 raise CannotChangeSubscription(
127 "%s does not own the email address: %s"
128 % (person.displayname, address.email)
129@@ -645,7 +645,7 @@ class MailingListSet:
130 Team = ClassAlias(Person)
131 tables = (
132 EmailAddress,
133- Join(Person, Person.id == EmailAddress.personID),
134+ Join(Person, Person.id == EmailAddress.person_id),
135 Join(Account, Account.id == Person.account_id),
136 Join(TeamParticipation, TeamParticipation.person_id == Person.id),
137 Join(
138@@ -702,7 +702,7 @@ class MailingListSet:
139 tables = (
140 Person,
141 Join(Account, Account.id == Person.account_id),
142- Join(EmailAddress, EmailAddress.personID == Person.id),
143+ Join(EmailAddress, EmailAddress.person_id == Person.id),
144 Join(TeamParticipation, TeamParticipation.person_id == Person.id),
145 Join(
146 MailingList, MailingList.team_id == TeamParticipation.team_id
147@@ -728,7 +728,7 @@ class MailingListSet:
148 tables = (
149 Person,
150 Join(Account, Account.id == Person.account_id),
151- Join(EmailAddress, EmailAddress.personID == Person.id),
152+ Join(EmailAddress, EmailAddress.person_id == Person.id),
153 Join(MessageApproval, MessageApproval.posted_by_id == Person.id),
154 Join(
155 MailingList, MailingList.id == MessageApproval.mailing_list_id
156diff --git a/lib/lp/registry/model/person.py b/lib/lp/registry/model/person.py
157index 1aa22bc..4846cd9 100644
158--- a/lib/lp/registry/model/person.py
159+++ b/lib/lp/registry/model/person.py
160@@ -2109,11 +2109,7 @@ class Person(
161 )
162
163 def _getEmailsByStatus(self, status):
164- return Store.of(self).find(
165- EmailAddress,
166- EmailAddress.personID == self.id,
167- EmailAddress.status == status,
168- )
169+ return Store.of(self).find(EmailAddress, person=self, status=status)
170
171 def checkInclusiveMembershipPolicyAllowed(self, policy="open"):
172 """See `ITeam`"""
173@@ -2258,7 +2254,7 @@ class Person(
174 LeftJoin(
175 email_table,
176 And(
177- email_table.personID == person_table.id,
178+ email_table.person_id == person_table.id,
179 email_table.status == EmailAddressStatus.PREFERRED,
180 ),
181 )
182@@ -3041,10 +3037,8 @@ class Person(
183 "Any person's email address must provide the IEmailAddress "
184 "interface. %s doesn't." % email
185 )
186- # XXX Steve Alexander 2005-07-05:
187- # This is here because of an SQLobject comparison oddity.
188- assert email.personID == self.id, "Wrong person! %r, %r" % (
189- email.personID,
190+ assert email.person == self, "Wrong person! %r, %r" % (
191+ email.person_id,
192 self.id,
193 )
194
195@@ -3056,7 +3050,7 @@ class Person(
196 IStore(EmailAddress)
197 .find(
198 EmailAddress,
199- EmailAddress.personID == self.id,
200+ EmailAddress.person == self,
201 EmailAddress.status == EmailAddressStatus.PREFERRED,
202 )
203 .one()
204@@ -3095,9 +3089,7 @@ class Person(
205 )
206 else:
207 mailing_list_email = None
208- all_addresses = IStore(EmailAddress).find(
209- EmailAddress, EmailAddress.personID == self.id
210- )
211+ all_addresses = IStore(EmailAddress).find(EmailAddress, person=self)
212 for address in all_addresses:
213 # Delete all email addresses that are not the preferred email
214 # address, or the team's email address. If this method was called
215@@ -3112,7 +3104,7 @@ class Person(
216 IStore(EmailAddress)
217 .find(
218 EmailAddress,
219- personID=self.id,
220+ person=self,
221 status=EmailAddressStatus.PREFERRED,
222 )
223 .one()
224@@ -3143,12 +3135,12 @@ class Person(
225 "Any person's email address must provide the IEmailAddress "
226 "interface. %s doesn't." % email
227 )
228- assert email.personID == self.id
229+ assert email.person == self
230 existing_preferred_email = (
231 IStore(EmailAddress)
232 .find(
233 EmailAddress,
234- personID=self.id,
235+ person=self,
236 status=EmailAddressStatus.PREFERRED,
237 )
238 .one()
239@@ -4520,7 +4512,7 @@ class PersonSet:
240 return (
241 IStore(Person)
242 .using(
243- Person, Join(EmailAddress, EmailAddress.personID == Person.id)
244+ Person, Join(EmailAddress, EmailAddress.person_id == Person.id)
245 )
246 .find(
247 (EmailAddress, Person),
248@@ -5543,7 +5535,7 @@ def _get_recipients_for_team(team):
249 # Find Persons that have a preferred email address and an active
250 # account, or are a team, or both.
251 intermediate_transitive_results = source.find(
252- (TeamMembership.person_id, EmailAddress.personID),
253+ (TeamMembership.person_id, EmailAddress.person_id),
254 TeamMembership.status.is_in(
255 (
256 TeamMembershipStatus.ADMIN,
257@@ -5553,7 +5545,7 @@ def _get_recipients_for_team(team):
258 TeamMembership.team_id.is_in(pending_team_ids),
259 Or(
260 And(
261- EmailAddress.personID != None,
262+ EmailAddress.person != None,
263 Account.status == AccountStatus.ACTIVE,
264 ),
265 Person.teamownerID != None,
266diff --git a/lib/lp/registry/scripts/closeaccount.py b/lib/lp/registry/scripts/closeaccount.py
267index d744b92..c7d4c55 100644
268--- a/lib/lp/registry/scripts/closeaccount.py
269+++ b/lib/lp/registry/scripts/closeaccount.py
270@@ -60,7 +60,7 @@ def close_account(username, log):
271
272 person = (
273 store.using(
274- Person, LeftJoin(EmailAddress, Person.id == EmailAddress.personID)
275+ Person, LeftJoin(EmailAddress, Person.id == EmailAddress.person_id)
276 )
277 .find(
278 Person,
279@@ -218,7 +218,7 @@ def close_account(username, log):
280 # people requesting account removal seem to primarily be interested
281 # in ensuring we no longer store this information.
282 table_notification("EmailAddress")
283- store.find(EmailAddress, EmailAddress.personID == person.id).remove()
284+ store.find(EmailAddress, person=person).remove()
285
286 # Clean out personal details from the Person table
287 table_notification("Person")
288diff --git a/lib/lp/registry/stories/teammembership/xx-teammembership.rst b/lib/lp/registry/stories/teammembership/xx-teammembership.rst
289index 3912580..a7b5a3b 100644
290--- a/lib/lp/registry/stories/teammembership/xx-teammembership.rst
291+++ b/lib/lp/registry/stories/teammembership/xx-teammembership.rst
292@@ -88,11 +88,12 @@ the team's home page.
293 If this was a moderated team, the membership would not have been automatically
294 approved, though.
295
296+ >>> from storm.locals import Store
297 >>> from lp.registry.interfaces.person import TeamMembershipPolicy
298 >>> from lp.registry.model.person import Person
299 >>> myemail = Person.selectOneBy(name="myemail")
300 >>> myemail.membership_policy = TeamMembershipPolicy.MODERATED
301- >>> myemail.syncUpdate()
302+ >>> Store.of(myemail).flush()
303
304 >>> browser = setupBrowser(
305 ... auth="Basic james.blackwell@ubuntulinux.com:test"
306@@ -142,7 +143,7 @@ Delegated teams also require approval of direct membership.
307
308 >>> login("test@canonical.com")
309 >>> myemail.membership_policy = TeamMembershipPolicy.DELEGATED
310- >>> myemail.syncUpdate()
311+ >>> Store.of(myemail).flush()
312 >>> logout()
313
314 >>> browser = setupBrowser(auth="Basic colin.watson@ubuntulinux.com:test")
315@@ -180,7 +181,7 @@ Delegated teams also require approval of direct membership.
316 If it was a restricted team, users wouldn't even see a link to join the team.
317
318 >>> myemail.membership_policy = TeamMembershipPolicy.RESTRICTED
319- >>> myemail.syncUpdate()
320+ >>> Store.of(myemail).flush()
321
322 >>> browser = setupBrowser(auth="Basic jeff.waugh@ubuntulinux.com:test")
323 >>> browser.open("http://launchpad.test/~myemail")
324diff --git a/lib/lp/registry/tests/test_person_merge_job.py b/lib/lp/registry/tests/test_person_merge_job.py
325index 319ed86..b3a5f99 100644
326--- a/lib/lp/registry/tests/test_person_merge_job.py
327+++ b/lib/lp/registry/tests/test_person_merge_job.py
328@@ -48,8 +48,7 @@ def transfer_email(job):
329 IPersonSet.merge() does not (yet) promise to do this.
330 """
331 from_email = removeSecurityProxy(job.from_person.preferredemail)
332- from_email.personID = job.to_person.id
333- from_email.account_id = job.to_person.account_id
334+ from_email.person = job.to_person
335 from_email.status = EmailAddressStatus.NEW
336 IStore(from_email).flush()
337
338diff --git a/lib/lp/registry/tests/test_personset.py b/lib/lp/registry/tests/test_personset.py
339index dc8f36f..fc8650f 100644
340--- a/lib/lp/registry/tests/test_personset.py
341+++ b/lib/lp/registry/tests/test_personset.py
342@@ -368,7 +368,6 @@ class TestPersonSetCreateByOpenId(TestCaseWithFactory):
343 return self.store.add(
344 EmailAddress(
345 email=email,
346- account=person.account,
347 person=person,
348 status=EmailAddressStatus.PREFERRED,
349 )
350@@ -388,7 +387,6 @@ class TestPersonSetCreateByOpenId(TestCaseWithFactory):
351 self.assertIs(self.person, found)
352 self.assertIs(self.account, found.account)
353 self.assertIs(self.email, found.preferredemail)
354- self.assertIs(self.email.account, self.account)
355 self.assertIs(self.email.person, self.person)
356 self.assertEqual(
357 [self.identifier], list(self.account.openid_identifiers)
358@@ -410,7 +408,6 @@ class TestPersonSetCreateByOpenId(TestCaseWithFactory):
359 self.assertIs(self.person, found)
360 self.assertIs(self.account, found.account)
361 self.assertIs(self.email, found.preferredemail)
362- self.assertIs(self.email.account, self.account)
363 self.assertIs(self.email.person, self.person)
364 self.assertEqual(
365 [self.identifier], list(self.account.openid_identifiers)
366@@ -433,7 +430,6 @@ class TestPersonSetCreateByOpenId(TestCaseWithFactory):
367 self.assertIs(self.person, found)
368 self.assertIs(self.account, found.account)
369 self.assertIs(self.email, found.preferredemail)
370- self.assertIs(self.email.account, self.account)
371 self.assertIs(self.email.person, self.person)
372
373 # Old OpenId Identifier still attached.
374@@ -474,7 +470,6 @@ class TestPersonSetCreateByOpenId(TestCaseWithFactory):
375 def testNoAccount(self):
376 # EmailAddress is linked to a Person, but there is no Account.
377 # Convert this stub into something valid.
378- self.email.account = None
379 self.email.status = EmailAddressStatus.NEW
380 self.person.account = None
381 new_identifier = "new_identifier"
382@@ -503,7 +498,6 @@ class TestPersonSetCreateByOpenId(TestCaseWithFactory):
383 self.identifier.account = self.store.find(
384 Account, displayname="Foo Bar"
385 ).one()
386- email_account = self.email.account
387
388 found, updated = self.person_set.getOrCreateByOpenIDIdentifier(
389 self.identifier.identifier,
390@@ -519,7 +513,6 @@ class TestPersonSetCreateByOpenId(TestCaseWithFactory):
391
392 self.assertIs(found.account, self.identifier.account)
393 self.assertIn(self.identifier, list(found.account.openid_identifiers))
394- self.assertIs(email_account, self.email.account)
395
396 def testEmptyOpenIDIdentifier(self):
397 self.assertRaises(
398diff --git a/lib/lp/registry/tests/test_team.py b/lib/lp/registry/tests/test_team.py
399index e00db68..5ab0e4f 100644
400--- a/lib/lp/registry/tests/test_team.py
401+++ b/lib/lp/registry/tests/test_team.py
402@@ -47,9 +47,7 @@ class TestTeamContactAddress(TestCaseWithFactory):
403
404 def getAllEmailAddresses(self):
405 transaction.commit()
406- all_addresses = self.store.find(
407- EmailAddress, EmailAddress.personID == self.team.id
408- )
409+ all_addresses = self.store.find(EmailAddress, person=self.team)
410 return [address for address in all_addresses.order_by("email")]
411
412 def createMailingListAndGetAddress(self):
413diff --git a/lib/lp/registry/vocabularies.py b/lib/lp/registry/vocabularies.py
414index 1c1c5f4..a733f84 100644
415--- a/lib/lp/registry/vocabularies.py
416+++ b/lib/lp/registry/vocabularies.py
417@@ -746,9 +746,11 @@ class ValidPersonOrTeamVocabulary(
418 # description so we need to bulk load them for performance, otherwise
419 # we get one query per person per attribute.
420 def pre_iter_hook(persons):
421- emails = bulk.load_referencing(EmailAddress, persons, ["personID"])
422+ emails = bulk.load_referencing(
423+ EmailAddress, persons, ["person_id"]
424+ )
425 email_by_person = {
426- email.personID: email
427+ email.person_id: email
428 for email in emails
429 if email.status == EmailAddressStatus.PREFERRED
430 }
431diff --git a/lib/lp/scripts/garbo.py b/lib/lp/scripts/garbo.py
432index f27f0b5..ef0a095 100644
433--- a/lib/lp/scripts/garbo.py
434+++ b/lib/lp/scripts/garbo.py
435@@ -1023,7 +1023,7 @@ class RevisionAuthorEmailLinker(TunableLoop):
436
437 emails = dict(
438 self.email_store.find(
439- (EmailAddress.email.lower(), EmailAddress.personID),
440+ (EmailAddress.email.lower(), EmailAddress.person_id),
441 EmailAddress.email.lower().is_in(
442 [author.email.lower() for author in authors]
443 ),
444@@ -1033,7 +1033,7 @@ class RevisionAuthorEmailLinker(TunableLoop):
445 EmailAddressStatus.VALIDATED,
446 ]
447 ),
448- EmailAddress.personID != None,
449+ EmailAddress.person != None,
450 )
451 )
452
453diff --git a/lib/lp/services/database/doc/storm.rst b/lib/lp/services/database/doc/storm.rst
454index e9b84c9..2c38e6c 100644
455--- a/lib/lp/services/database/doc/storm.rst
456+++ b/lib/lp/services/database/doc/storm.rst
457@@ -162,7 +162,7 @@ Objects not yet flushed to the database also compare equal.
458 >>> unflushed = EmailAddress(
459 ... email="notflushed@example.com",
460 ... status=EmailAddressStatus.NEW,
461- ... personID=1,
462+ ... person=getUtility(IPersonSet).get(1),
463 ... )
464 >>> unflushed == unflushed
465 True
466diff --git a/lib/lp/services/identity/configure.zcml b/lib/lp/services/identity/configure.zcml
467index 66aea4e..b54af81 100644
468--- a/lib/lp/services/identity/configure.zcml
469+++ b/lib/lp/services/identity/configure.zcml
470@@ -16,9 +16,7 @@
471 attributes="
472 id
473 person
474- personID
475- account
476- account_id
477+ person_id
478 status
479 rdf_sha1"/>
480 <require
481diff --git a/lib/lp/services/identity/interfaces/emailaddress.py b/lib/lp/services/identity/interfaces/emailaddress.py
482index cbd0198..450c96b 100644
483--- a/lib/lp/services/identity/interfaces/emailaddress.py
484+++ b/lib/lp/services/identity/interfaces/emailaddress.py
485@@ -120,7 +120,7 @@ class IEmailAddress(IHasOwner):
486 title=_("Person"), required=True, readonly=False, schema=Interface
487 )
488 )
489- personID = Int(title=_("PersonID"), required=True, readonly=True)
490+ person_id = Int(title=_("Person ID"), required=True, readonly=True)
491
492 rdf_sha1 = TextLine(
493 title=_("RDF-ready SHA-1 Hash"),
494@@ -139,13 +139,6 @@ class IEmailAddress(IHasOwner):
495 preferred one or a hosted mailing list's address.
496 """
497
498- def syncUpdate():
499- """Write updates made on this object to the database.
500-
501- This should be used when you can't wait until the transaction is
502- committed to have some updates actually written to the database.
503- """
504-
505
506 class IEmailAddressSet(Interface):
507 """The set of EmailAddresses."""
508diff --git a/lib/lp/services/identity/model/emailaddress.py b/lib/lp/services/identity/model/emailaddress.py
509index c80f10e..043a2f6 100644
510--- a/lib/lp/services/identity/model/emailaddress.py
511+++ b/lib/lp/services/identity/model/emailaddress.py
512@@ -12,15 +12,13 @@ __all__ = [
513 import hashlib
514 import operator
515
516-import six
517-from storm.expr import Lower
518+from storm.locals import Int, Reference, Unicode
519 from zope.interface import implementer
520
521 from lp.app.validators.email import valid_email
522 from lp.services.database.enumcol import DBEnum
523 from lp.services.database.interfaces import IPrimaryStore, IStore
524-from lp.services.database.sqlbase import SQLBase, sqlvalues
525-from lp.services.database.sqlobject import ForeignKey, StringCol
526+from lp.services.database.stormbase import StormBase
527 from lp.services.identity.interfaces.emailaddress import (
528 EmailAddressAlreadyTaken,
529 EmailAddressStatus,
530@@ -41,15 +39,21 @@ class HasOwnerMixin:
531
532
533 @implementer(IEmailAddress)
534-class EmailAddress(SQLBase, HasOwnerMixin):
535- _table = "EmailAddress"
536- _defaultOrder = ["email"]
537+class EmailAddress(StormBase, HasOwnerMixin):
538+ __storm_table__ = "EmailAddress"
539+ __storm_order__ = ["email"]
540
541- email = StringCol(
542- dbName="email", notNull=True, unique=True, alternateID=True
543- )
544+ id = Int(primary=True)
545+ email = Unicode(name="email", allow_none=False)
546 status = DBEnum(name="status", enum=EmailAddressStatus, allow_none=False)
547- person = ForeignKey(dbName="person", foreignKey="Person", notNull=False)
548+ person_id = Int(name="person", allow_none=True)
549+ person = Reference(person_id, "Person.id")
550+
551+ def __init__(self, email, status, person=None):
552+ super().__init__()
553+ self.email = email
554+ self.status = status
555+ self.person = person
556
557 def __repr__(self):
558 return "<EmailAddress <%s> [%s]>" % (self.email, self.status)
559@@ -83,7 +87,7 @@ class EmailAddress(SQLBase, HasOwnerMixin):
560 MailingListSubscription, email_address=self
561 ):
562 store.remove(subscription)
563- super().destroySelf()
564+ store.remove(self)
565
566 @property
567 def rdf_sha1(self):
568@@ -99,18 +103,18 @@ class EmailAddress(SQLBase, HasOwnerMixin):
569 class EmailAddressSet:
570 def getByPerson(self, person):
571 """See `IEmailAddressSet`."""
572- return EmailAddress.selectBy(person=person, orderBy="email")
573+ return (
574+ IStore(EmailAddress)
575+ .find(EmailAddress, person=person)
576+ .order_by(EmailAddress.email)
577+ )
578
579 def getPreferredEmailForPeople(self, people):
580 """See `IEmailAddressSet`."""
581- return EmailAddress.select(
582- """
583- EmailAddress.status = %s AND
584- EmailAddress.person IN %s
585- """
586- % sqlvalues(
587- EmailAddressStatus.PREFERRED, [person.id for person in people]
588- )
589+ return IStore(EmailAddress).find(
590+ EmailAddress,
591+ EmailAddress.status == EmailAddressStatus.PREFERRED,
592+ EmailAddress.person_id.is_in([person.id for person in people]),
593 )
594
595 def getByEmail(self, email):
596@@ -119,8 +123,7 @@ class EmailAddressSet:
597 IStore(EmailAddress)
598 .find(
599 EmailAddress,
600- Lower(EmailAddress.email)
601- == six.ensure_text(email).strip().lower(),
602+ EmailAddress.email.lower() == email.strip().lower(),
603 )
604 .one()
605 )
606diff --git a/lib/lp/services/verification/browser/logintoken.py b/lib/lp/services/verification/browser/logintoken.py
607index 8cb3b0c..7bdc469 100644
608--- a/lib/lp/services/verification/browser/logintoken.py
609+++ b/lib/lp/services/verification/browser/logintoken.py
610@@ -426,7 +426,7 @@ class ValidateEmailView(BaseTokenView, LaunchpadFormView):
611 emailset = getUtility(IEmailAddressSet)
612 email = emailset.getByEmail(self.context.email)
613 if email is not None:
614- if requester is None or email.personID != requester.id:
615+ if requester is None or email.person != requester:
616 dupe = email.person
617 # Yes, hardcoding an autogenerated field name is an evil
618 # hack, but if it fails nothing will happen.
619@@ -585,7 +585,7 @@ class MergePeopleView(BaseTokenView, LaunchpadFormView):
620 # As a person can have at most one preferred email, ensure
621 # that this new email does not have the PREFERRED status.
622 email.status = EmailAddressStatus.NEW
623- email.personID = requester.id
624+ email.person = requester
625 requester.validateAndEnsurePreferredEmail(email)
626
627 # Need to flush all changes we made, so subsequent queries we make
628@@ -595,7 +595,7 @@ class MergePeopleView(BaseTokenView, LaunchpadFormView):
629
630 # Now we must check if the dupe account still have registered email
631 # addresses. If it hasn't we can actually do the merge.
632- if emailset.getByPerson(self.dupe):
633+ if not emailset.getByPerson(self.dupe).is_empty():
634 self.mergeCompleted = False
635 return
636 getUtility(IPersonSet).mergeAsync(
637diff --git a/lib/lp/services/webapp/doc/webapp-publication.rst b/lib/lp/services/webapp/doc/webapp-publication.rst
638index 2473f52..6a54055 100644
639--- a/lib/lp/services/webapp/doc/webapp-publication.rst
640+++ b/lib/lp/services/webapp/doc/webapp-publication.rst
641@@ -1022,7 +1022,7 @@ be automatically reverted in a GET request.
642 ... IPrimaryStore(Person)
643 ... .find(
644 ... Person,
645- ... Person.id == EmailAddress.personID,
646+ ... Person.id == EmailAddress.person_id,
647 ... EmailAddress.email == "foo.bar@canonical.com",
648 ... )
649 ... .one()
650diff --git a/lib/lp/soyuz/doc/gina-multiple-arch.rst b/lib/lp/soyuz/doc/gina-multiple-arch.rst
651index e2d84b1..86b6820 100644
652--- a/lib/lp/soyuz/doc/gina-multiple-arch.rst
653+++ b/lib/lp/soyuz/doc/gina-multiple-arch.rst
654@@ -23,7 +23,7 @@ Get the current counts of stuff in the database:
655 >>> orig_tp_count = (
656 ... IStore(TeamParticipation).find(TeamParticipation).count()
657 ... )
658- >>> orig_email_count = EmailAddress.select().count()
659+ >>> orig_email_count = IStore(EmailAddress).find(EmailAddress).count()
660 >>> orig_bpr_count = (
661 ... IStore(BinaryPackageRelease).find(BinaryPackageRelease).count()
662 ... )
663@@ -147,7 +147,9 @@ porridge):
664 ... - orig_tp_count
665 ... )
666 2
667- >>> print(EmailAddress.select().count() - orig_email_count)
668+ >>> print(
669+ ... IStore(EmailAddress).find(EmailAddress).count() - orig_email_count
670+ ... )
671 2
672
673 There are 4 binary packages generated by the two builds of the two
674diff --git a/lib/lp/soyuz/doc/gina.rst b/lib/lp/soyuz/doc/gina.rst
675index a934313..bea1206 100644
676--- a/lib/lp/soyuz/doc/gina.rst
677+++ b/lib/lp/soyuz/doc/gina.rst
678@@ -30,7 +30,7 @@ Get the current counts of stuff in the database:
679 >>> orig_tp_count = (
680 ... IStore(TeamParticipation).find(TeamParticipation).count()
681 ... )
682- >>> orig_email_count = EmailAddress.select().count()
683+ >>> orig_email_count = IStore(EmailAddress).find(EmailAddress).count()
684 >>> orig_bpr_count = (
685 ... IStore(BinaryPackageRelease).find(BinaryPackageRelease).count()
686 ... )
687@@ -599,7 +599,9 @@ Kamion, 2 being uploaded by mdz and 2 by doko).
688 ... - orig_tp_count
689 ... )
690 13
691- >>> print(EmailAddress.select().count() - orig_email_count)
692+ >>> print(
693+ ... IStore(EmailAddress).find(EmailAddress).count() - orig_email_count
694+ ... )
695 13
696
697
698@@ -695,7 +697,9 @@ changed, etc.
699 ... - orig_tp_count
700 ... )
701 13
702- >>> print(EmailAddress.select().count() - orig_email_count)
703+ >>> print(
704+ ... IStore(EmailAddress).find(EmailAddress).count() - orig_email_count
705+ ... )
706 13
707 >>> (
708 ... IStore(BinaryPackageRelease).find(BinaryPackageRelease).count()
709diff --git a/lib/lp/soyuz/model/archivesubscriber.py b/lib/lp/soyuz/model/archivesubscriber.py
710index d57cf23..0ff0cc7 100644
711--- a/lib/lp/soyuz/model/archivesubscriber.py
712+++ b/lib/lp/soyuz/model/archivesubscriber.py
713@@ -116,7 +116,7 @@ class ArchiveSubscriber(StormBase):
714
715 # Only return people with preferred email address set.
716 preferred_email = Join(
717- EmailAddress, EmailAddress.personID == Person.id
718+ EmailAddress, EmailAddress.person_id == Person.id
719 )
720
721 # We want to get all participants who are themselves
722@@ -151,7 +151,7 @@ class ArchiveSubscriber(StormBase):
723 return store.find(
724 (Person, EmailAddress),
725 Person.id == self.subscriber_id,
726- EmailAddress.personID == Person.id,
727+ EmailAddress.person_id == Person.id,
728 EmailAddress.status == EmailAddressStatus.PREFERRED,
729 )
730
731diff --git a/lib/lp/soyuz/tests/test_doc.py b/lib/lp/soyuz/tests/test_doc.py
732index f316ab8..936370b 100644
733--- a/lib/lp/soyuz/tests/test_doc.py
734+++ b/lib/lp/soyuz/tests/test_doc.py
735@@ -12,6 +12,9 @@ import unittest
736 import transaction
737
738 from lp.services.config import config
739+from lp.services.database.interfaces import IStore
740+from lp.services.identity.interfaces.emailaddress import EmailAddressStatus
741+from lp.services.identity.model.emailaddress import EmailAddress
742 from lp.testing import logout
743 from lp.testing.dbuser import switch_dbuser
744 from lp.testing.layers import LaunchpadFunctionalLayer, LaunchpadZopelessLayer
745@@ -35,11 +38,10 @@ def lobotomize_stevea():
746 code that did not use the ValidPersonOrTeamCache to determine
747 validity.
748 """
749- from lp.services.identity.interfaces.emailaddress import EmailAddressStatus
750- from lp.services.identity.model.emailaddress import EmailAddress
751-
752- stevea_emailaddress = EmailAddress.byEmail(
753- "steve.alexander@ubuntulinux.com"
754+ stevea_emailaddress = (
755+ IStore(EmailAddress)
756+ .find(EmailAddress, email="steve.alexander@ubuntulinux.com")
757+ .one()
758 )
759 stevea_emailaddress.status = EmailAddressStatus.NEW
760 transaction.commit()

Subscribers

People subscribed via source and target branches

to status/vote changes: