← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:stormify-account into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:stormify-account into launchpad:master.

Commit message:
Convert Account to Storm

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/446388
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:stormify-account into launchpad:master.
diff --git a/lib/lp/registry/doc/person.rst b/lib/lp/registry/doc/person.rst
index 593fd24..eb3075a 100644
--- a/lib/lp/registry/doc/person.rst
+++ b/lib/lp/registry/doc/person.rst
@@ -147,7 +147,7 @@ using the createPersonAndEmail() method.
 
     >>> from lp.services.identity.model.account import Account
     >>> from lp.services.database.interfaces import IPrimaryStore
-    >>> account = IPrimaryStore(Account).get(Account, p.accountID)
+    >>> account = IPrimaryStore(Account).get(Account, p.account_id)
     >>> account.reactivate("Activated by doc test.")
     >>> p.account_status
     <DBItem AccountStatus.ACTIVE...
diff --git a/lib/lp/registry/interfaces/person.py b/lib/lp/registry/interfaces/person.py
index 383d604..7cdc22a 100644
--- a/lib/lp/registry/interfaces/person.py
+++ b/lib/lp/registry/interfaces/person.py
@@ -849,7 +849,7 @@ class IPersonViewRestricted(
     """IPerson attributes that require launchpad.View permission."""
 
     account = Object(schema=IAccount)
-    accountID = Int(title=_("Account ID"), required=True, readonly=True)
+    account_id = Int(title=_("Account ID"), required=True, readonly=True)
     karma = exported(
         Int(
             title=_("Karma"),
diff --git a/lib/lp/registry/model/mailinglist.py b/lib/lp/registry/model/mailinglist.py
index ccc31dd..c47b266 100644
--- a/lib/lp/registry/model/mailinglist.py
+++ b/lib/lp/registry/model/mailinglist.py
@@ -646,7 +646,7 @@ class MailingListSet:
         tables = (
             EmailAddress,
             Join(Person, Person.id == EmailAddress.personID),
-            Join(Account, Account.id == Person.accountID),
+            Join(Account, Account.id == Person.account_id),
             Join(TeamParticipation, TeamParticipation.personID == Person.id),
             Join(
                 MailingListSubscription,
@@ -701,7 +701,7 @@ class MailingListSet:
         Team = ClassAlias(Person)
         tables = (
             Person,
-            Join(Account, Account.id == Person.accountID),
+            Join(Account, Account.id == Person.account_id),
             Join(EmailAddress, EmailAddress.personID == Person.id),
             Join(TeamParticipation, TeamParticipation.personID == Person.id),
             Join(MailingList, MailingList.team_id == TeamParticipation.teamID),
@@ -725,7 +725,7 @@ class MailingListSet:
         # for three global approvals.
         tables = (
             Person,
-            Join(Account, Account.id == Person.accountID),
+            Join(Account, Account.id == Person.account_id),
             Join(EmailAddress, EmailAddress.personID == Person.id),
             Join(MessageApproval, MessageApproval.posted_by_id == Person.id),
             Join(
diff --git a/lib/lp/registry/model/person.py b/lib/lp/registry/model/person.py
index 53e2358..a868a2c 100644
--- a/lib/lp/registry/model/person.py
+++ b/lib/lp/registry/model/person.py
@@ -468,7 +468,8 @@ class Person(
     _visibility_warning_cache_key = None
     _visibility_warning_cache = None
 
-    account = ForeignKey(dbName="account", foreignKey="Account", default=None)
+    account_id = Int(name="account", default=None)
+    account = Reference(account_id, "Account.id")
 
     def _validate_name(self, attr, value):
         """Check that rename is allowed."""
@@ -716,9 +717,9 @@ class Person(
         )
         # Teams don't have Account records
         if self.account is not None:
-            account_id = self.account.id
+            account = self.account
             self.account = None
-            Account.delete(account_id)
+            Store.of(account).remove(account)
         self.creation_rationale = None
         self.teamowner = team_owner
         alsoProvides(self, ITeam)
@@ -2245,7 +2246,7 @@ class Person(
             LeftJoin(
                 account_table,
                 And(
-                    person_table.accountID == account_table.id,
+                    person_table.account_id == account_table.id,
                     account_table.status == AccountStatus.ACTIVE,
                 ),
             )
@@ -4249,7 +4250,7 @@ class PersonSet:
         person = Person(
             name=name,
             display_name=displayname,
-            accountID=account_id,
+            account_id=account_id,
             creation_rationale=rationale,
             creation_comment=comment,
             hide_email_addresses=hide_email_addresses,
@@ -4289,7 +4290,7 @@ class PersonSet:
 
     def getByAccount(self, account):
         """See `IPersonSet`."""
-        return Person.selectOne(Person.q.accountID == account.id)
+        return IStore(Person).find(Person, account_id=account.id).one()
 
     def updateStatistics(self):
         """See `IPersonSet`."""
@@ -5502,7 +5503,7 @@ def _get_recipients_for_team(team):
                 EmailAddress.status == EmailAddressStatus.PREFERRED,
             ),
         ),
-        LeftJoin(Account, Person.accountID == Account.id),
+        LeftJoin(Account, Person.account_id == Account.id),
     )
     pending_team_ids = [team.id]
     recipient_ids = set()
diff --git a/lib/lp/registry/personmerge.py b/lib/lp/registry/personmerge.py
index 3318c6b..cec406c 100644
--- a/lib/lp/registry/personmerge.py
+++ b/lib/lp/registry/personmerge.py
@@ -1245,7 +1245,7 @@ def merge_people(from_person, to_person, reviewer, delete=False):
             """
             UPDATE OpenIdIdentifier SET account=%s WHERE account=%s
             """
-            % sqlvalues(to_person.accountID, from_person.accountID)
+            % sqlvalues(to_person.account_id, from_person.account_id)
         )
 
     if delete:
diff --git a/lib/lp/registry/security.py b/lib/lp/registry/security.py
index a453edc..03692fe 100644
--- a/lib/lp/registry/security.py
+++ b/lib/lp/registry/security.py
@@ -168,7 +168,7 @@ class EditAccountBySelfOrAdmin(AuthorizationBase):
     usedfor = IAccount
 
     def checkAuthenticated(self, user):
-        return user.in_admin or user.person.accountID == self.obj.id
+        return user.in_admin or user.person.account == self.obj
 
 
 class ViewAccount(EditAccountBySelfOrAdmin):
diff --git a/lib/lp/registry/tests/test_person_merge_job.py b/lib/lp/registry/tests/test_person_merge_job.py
index 6db2384..319ed86 100644
--- a/lib/lp/registry/tests/test_person_merge_job.py
+++ b/lib/lp/registry/tests/test_person_merge_job.py
@@ -49,7 +49,7 @@ def transfer_email(job):
     """
     from_email = removeSecurityProxy(job.from_person.preferredemail)
     from_email.personID = job.to_person.id
-    from_email.accountID = job.to_person.accountID
+    from_email.account_id = job.to_person.account_id
     from_email.status = EmailAddressStatus.NEW
     IStore(from_email).flush()
 
diff --git a/lib/lp/registry/tests/test_personset.py b/lib/lp/registry/tests/test_personset.py
index 07acc60..dc8f36f 100644
--- a/lib/lp/registry/tests/test_personset.py
+++ b/lib/lp/registry/tests/test_personset.py
@@ -344,13 +344,15 @@ class TestPersonSetCreateByOpenId(TestCaseWithFactory):
         )
 
     def makeAccount(self):
-        return self.store.add(
+        account = self.store.add(
             Account(
                 displayname="Displayname",
                 creation_rationale=AccountCreationRationale.UNKNOWN,
                 status=AccountStatus.ACTIVE,
             )
         )
+        self.store.flush()
+        return account
 
     def makePerson(self, account):
         return self.store.add(
diff --git a/lib/lp/registry/vocabularies.py b/lib/lp/registry/vocabularies.py
index 9391add..a9acabf 100644
--- a/lib/lp/registry/vocabularies.py
+++ b/lib/lp/registry/vocabularies.py
@@ -694,7 +694,7 @@ class ValidPersonOrTeamVocabulary(
                 SQL("MatchingPerson"),
                 Person,
                 LeftJoin(EmailAddress, EmailAddress.person == Person.id),
-                LeftJoin(Account, Account.id == Person.accountID),
+                LeftJoin(Account, Account.id == Person.account_id),
             ]
             tables.extend(self.extra_tables)
 
diff --git a/lib/lp/services/auth/tests/test_browser.py b/lib/lp/services/auth/tests/test_browser.py
index 3d237c0..a1aba38 100644
--- a/lib/lp/services/auth/tests/test_browser.py
+++ b/lib/lp/services/auth/tests/test_browser.py
@@ -54,7 +54,7 @@ class TestAccessTokenViewBase:
         # in create_view instead, but that approach needs care to avoid
         # adding an extra query to tests that might be sensitive to that.
         principal = getUtility(IPlacelessAuthUtility).getPrincipal(
-            self.owner.accountID
+            self.owner.account_id
         )
         view = create_view(
             self.target,
diff --git a/lib/lp/services/identity/configure.zcml b/lib/lp/services/identity/configure.zcml
index 690b52f..66aea4e 100644
--- a/lib/lp/services/identity/configure.zcml
+++ b/lib/lp/services/identity/configure.zcml
@@ -18,7 +18,7 @@
                 person
                 personID
                 account
-                accountID
+                account_id
                 status
                 rdf_sha1"/>
         <require
diff --git a/lib/lp/services/identity/doc/account.rst b/lib/lp/services/identity/doc/account.rst
index 69a3770..735e890 100644
--- a/lib/lp/services/identity/doc/account.rst
+++ b/lib/lp/services/identity/doc/account.rst
@@ -14,6 +14,7 @@ implements the IAccountSet interface.
 
     >>> from zope.interface.verify import verifyObject
     >>> from lp.registry.interfaces.person import IPersonSet
+    >>> from lp.services.database.interfaces import IStore
     >>> from lp.services.identity.interfaces.account import (
     ...     IAccount,
     ...     IAccountSet,
@@ -116,8 +117,12 @@ database. Only an admin can change the status.
     >>> login("admin@xxxxxxxxxxxxx")
     >>> account.setStatus(AccountStatus.SUSPENDED, None, "spammer")
 
-    # Shouldn't be necessary with Storm!
-    >>> removeSecurityProxy(account).sync()
+date_status_set is maintained by a DB trigger, so we need to flush the
+status change and force the Account row to be reloaded from the database in
+order to check that the trigger works.
+
+    >>> IStore(account).flush()
+    >>> IStore(account).autoreload(account)
     >>> account.date_status_set > original_date_status_set
     True
 
diff --git a/lib/lp/services/identity/interfaces/account.py b/lib/lp/services/identity/interfaces/account.py
index eaff4a7..99615be 100644
--- a/lib/lp/services/identity/interfaces/account.py
+++ b/lib/lp/services/identity/interfaces/account.py
@@ -355,7 +355,7 @@ class AccountStatusChoice(Choice):
         if not IAccount.providedBy(self.context):
             # Not an account, eg. validating Person.setAccountStatus.
             return True
-        if removeSecurityProxy(self.context)._SO_creating:
+        if removeSecurityProxy(self.context)._creating:
             # This object is initializing.
             return True
         if self.context.status == value:
diff --git a/lib/lp/services/identity/model/account.py b/lib/lp/services/identity/model/account.py
index 5412f60..9096b01 100644
--- a/lib/lp/services/identity/model/account.py
+++ b/lib/lp/services/identity/model/account.py
@@ -8,17 +8,15 @@ __all__ = [
     "AccountSet",
 ]
 
-import datetime
+from datetime import datetime, timezone
 
-from storm.locals import ReferenceSet
+from storm.locals import DateTime, Int, ReferenceSet, Unicode
 from zope.interface import implementer
 
 from lp.services.database.constants import UTC_NOW
-from lp.services.database.datetimecol import UtcDateTimeCol
 from lp.services.database.enumcol import DBEnum
 from lp.services.database.interfaces import IPrimaryStore, IStore
-from lp.services.database.sqlbase import SQLBase
-from lp.services.database.sqlobject import StringCol
+from lp.services.database.stormbase import StormBase
 from lp.services.helpers import backslashreplace
 from lp.services.identity.interfaces.account import (
     AccountCreationRationale,
@@ -38,12 +36,21 @@ class AccountStatusDBEnum(DBEnum):
 
 
 @implementer(IAccount)
-class Account(SQLBase):
+class Account(StormBase):
     """An Account."""
 
-    date_created = UtcDateTimeCol(notNull=True, default=UTC_NOW)
+    __storm_table__ = "Account"
 
-    displayname = StringCol(dbName="displayname", notNull=True)
+    id = Int(primary=True)
+
+    date_created = DateTime(
+        name="date_created",
+        allow_none=False,
+        default=UTC_NOW,
+        tzinfo=timezone.utc,
+    )
+
+    displayname = Unicode(name="displayname", allow_none=False)
 
     creation_rationale = DBEnum(
         name="creation_rationale",
@@ -51,15 +58,33 @@ class Account(SQLBase):
         allow_none=False,
     )
     status = AccountStatusDBEnum(
-        enum=AccountStatus, default=AccountStatus.NOACCOUNT, allow_none=False
+        name="status",
+        enum=AccountStatus,
+        default=AccountStatus.NOACCOUNT,
+        allow_none=False,
     )
-    date_status_set = UtcDateTimeCol(notNull=True, default=UTC_NOW)
-    status_history = StringCol(dbName="status_comment", default=None)
+    date_status_set = DateTime(
+        name="date_status_set",
+        allow_none=False,
+        default=UTC_NOW,
+        tzinfo=timezone.utc,
+    )
+    status_history = Unicode(name="status_comment", default=None)
 
     openid_identifiers = ReferenceSet(
         "Account.id", OpenIdIdentifier.account_id
     )
 
+    _creating = False
+
+    def __init__(self, displayname, creation_rationale, status):
+        super().__init__()
+        self._creating = True
+        self.displayname = displayname
+        self.creation_rationale = creation_rationale
+        self.status = status
+        del self._creating
+
     def __repr__(self):
         displayname = backslashreplace(self.displayname)
         return "<%s '%s' (%s)>" % (
@@ -70,7 +95,7 @@ class Account(SQLBase):
 
     def addStatusComment(self, user, comment):
         """See `IAccountModerateRestricted`."""
-        prefix = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
+        prefix = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
         if user is not None:
             prefix += " %s" % user.name
         old_lines = (
@@ -112,6 +137,8 @@ class AccountSet:
             creation_rationale=rationale,
             status=status,
         )
+        IStore(Account).add(account)
+        IStore(Account).flush()
 
         # Create an OpenIdIdentifier record if requested.
         if openid_identifier is not None:
diff --git a/lib/lp/services/openid/security.py b/lib/lp/services/openid/security.py
index 6c9183e..d794603 100644
--- a/lib/lp/services/openid/security.py
+++ b/lib/lp/services/openid/security.py
@@ -14,4 +14,4 @@ class ViewOpenIdIdentifierBySelfOrAdmin(AuthorizationBase):
     usedfor = IOpenIdIdentifier
 
     def checkAuthenticated(self, user):
-        return user.in_admin or user.person.accountID == self.obj.accountID
+        return user.in_admin or user.person.account_id == self.obj.account_id
diff --git a/lib/lp/services/webapp/authentication.py b/lib/lp/services/webapp/authentication.py
index b40d4af..e7c84ad 100644
--- a/lib/lp/services/webapp/authentication.py
+++ b/lib/lp/services/webapp/authentication.py
@@ -88,8 +88,8 @@ class PlacelessAuthUtility:
             person_id = authdata.get("personid")
             if person_id is not None:
                 person = getUtility(IPersonSet).get(person_id)
-                if person is not None and person.accountID is not None:
-                    id = person.accountID
+                if person is not None and person.account_id is not None:
+                    id = person.account_id
 
         if id is None:
             return None
diff --git a/lib/lp/services/webapp/servers.py b/lib/lp/services/webapp/servers.py
index ffed710..53db3f4 100644
--- a/lib/lp/services/webapp/servers.py
+++ b/lib/lp/services/webapp/servers.py
@@ -1244,7 +1244,7 @@ class WebServicePublication(
         alsoProvides(request, IAccessTokenVerifiedRequest)
         get_interaction_extras().access_token = access_token
         return getUtility(IPlacelessLoginSource).getPrincipal(
-            access_token.owner.accountID
+            access_token.owner.account_id
         )
 
     def _getPrincipalFromOAuth(self, request):
diff --git a/lib/lp/services/webhooks/tests/test_browser.py b/lib/lp/services/webhooks/tests/test_browser.py
index 04f4654..f8a76a7 100644
--- a/lib/lp/services/webhooks/tests/test_browser.py
+++ b/lib/lp/services/webhooks/tests/test_browser.py
@@ -222,7 +222,7 @@ class WebhookTargetViewTestHelpers:
         # in create_view instead, but that approach needs care to avoid
         # adding an extra query to tests that might be sensitive to that.
         principal = getUtility(IPlacelessAuthUtility).getPrincipal(
-            self.owner.accountID
+            self.owner.account_id
         )
         view = create_view(
             self.target,
diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py
index 8764715..7329dda 100644
--- a/lib/lp/testing/factory.py
+++ b/lib/lp/testing/factory.py
@@ -681,7 +681,7 @@ class LaunchpadObjectFactory(ObjectFactory):
         # To make the person someone valid in Launchpad, validate the
         # email.
         if email_address_status == EmailAddressStatus.PREFERRED:
-            account = IPrimaryStore(Account).get(Account, person.accountID)
+            account = IPrimaryStore(Account).get(Account, person.account_id)
             account.status = AccountStatus.ACTIVE
             person.setPreferredEmail(email)
 
@@ -757,7 +757,7 @@ class LaunchpadObjectFactory(ObjectFactory):
         if set_preferred_email:
             # setPreferredEmail no longer activates the account
             # automatically.
-            account = IPrimaryStore(Account).get(Account, person.accountID)
+            account = IPrimaryStore(Account).get(Account, person.account_id)
             account.reactivate("Activated by factory.makePersonByName")
             person.setPreferredEmail(email)
 
@@ -768,7 +768,7 @@ class LaunchpadObjectFactory(ObjectFactory):
                 person.mailing_list_auto_subscribe_policy = (
                     MailingListAutoSubscribePolicy.NEVER
                 )
-        account = IPrimaryStore(Account).get(Account, person.accountID)
+        account = IPrimaryStore(Account).get(Account, person.account_id)
         getUtility(IEmailAddressSet).new(
             alternative_address, person, EmailAddressStatus.VALIDATED
         )