← Back to team overview

launchpad-reviewers team mailing list archive

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

 

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

Commit message:
Convert Person to Storm

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/451654
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:stormify-person into launchpad:master.
diff --git a/lib/lp/bugs/model/bugtasksearch.py b/lib/lp/bugs/model/bugtasksearch.py
index 8de8c55..08fe8dc 100644
--- a/lib/lp/bugs/model/bugtasksearch.py
+++ b/lib/lp/bugs/model/bugtasksearch.py
@@ -29,8 +29,9 @@ from storm.expr import (
     Row,
     Select,
     Union,
+    With,
 )
-from storm.info import ClassAlias
+from storm.info import ClassAlias, get_cls_info
 from storm.references import Reference
 from zope.component import getUtility
 from zope.security.proxy import isinstance as zope_isinstance
@@ -77,10 +78,6 @@ from lp.registry.model.teammembership import TeamParticipation
 from lp.services.database.bulk import load
 from lp.services.database.decoratedresultset import DecoratedResultSet
 from lp.services.database.interfaces import IStore
-from lp.services.database.sqlbase import (
-    convert_storm_clause_to_string,
-    sqlvalues,
-)
 from lp.services.database.stormexpr import (
     ArrayAgg,
     ArrayIntersects,
@@ -224,10 +221,10 @@ def search_bugs(pre_iter_hook, alternatives, just_bug_ids=False):
             clauseTables,
             bugtask_decorator,
             join_tables,
-            with_clause,
+            with_clauses,
         ] = _build_query(alternatives[0])
-        if with_clause:
-            store = store.with_(with_clause)
+        if with_clauses:
+            store = store.with_(with_clauses)
         decorators.append(bugtask_decorator)
         origin = _build_origin(
             join_tables + orderby_joins, clauseTables, start
@@ -242,12 +239,12 @@ def search_bugs(pre_iter_hook, alternatives, just_bug_ids=False):
                 clauseTables,
                 decorator,
                 join_tables,
-                with_clause,
+                with_clauses,
             ] = _build_query(params)
             origin = _build_origin(join_tables, clauseTables, start)
             localstore = store
-            if with_clause:
-                localstore = store.with_(with_clause)
+            if with_clauses:
+                localstore = store.with_(with_clauses)
             next_result = localstore.using(*origin).find(BugTaskFlat, query)
             results.append(next_result)
             # NB: assumes the decorators are all compatible.
@@ -492,9 +489,16 @@ def _build_query(params):
 
     if params.structural_subscriber is not None:
         with_clauses.append(
-            """ss as (SELECT * from StructuralSubscription
-            WHERE StructuralSubscription.subscriber = %s)"""
-            % sqlvalues(params.structural_subscriber)
+            With(
+                "ss",
+                Select(
+                    get_cls_info(StructuralSubscription).columns,
+                    where=(
+                        StructuralSubscription.subscriber
+                        == params.structural_subscriber
+                    ),
+                ),
+            )
         )
 
         class StructuralSubscriptionCTE(StructuralSubscription):
@@ -761,27 +765,23 @@ def _build_query(params):
         )
         store = IStore(Bug)
         with_clauses.append(
-            convert_storm_clause_to_string(
-                WithMaterialized(
-                    "commented_bug_ids",
-                    store,
-                    Union(commented_messages, commented_activities),
-                )
+            WithMaterialized(
+                "commented_bug_ids",
+                store,
+                Union(commented_messages, commented_activities),
             )
         )
         with_clauses.append(
-            convert_storm_clause_to_string(
-                WithMaterialized(
-                    "commented_bugtask_ids",
-                    store,
-                    Select(
-                        BugTaskFlat.bugtask_id,
-                        tables=[BugTaskFlat],
-                        where=BugTaskFlat.bug_id.is_in(
-                            Select(Column("bug", "commented_bug_ids"))
-                        ),
+            WithMaterialized(
+                "commented_bugtask_ids",
+                store,
+                Select(
+                    BugTaskFlat.bugtask_id,
+                    tables=[BugTaskFlat],
+                    where=BugTaskFlat.bug_id.is_in(
+                        Select(Column("bug", "commented_bug_ids"))
                     ),
-                )
+                ),
             )
         )
         extra_clauses.append(
@@ -921,11 +921,7 @@ def _build_query(params):
                 obj = decor(obj)
             return obj
 
-    if with_clauses:
-        with_clause = SQL(", ".join(with_clauses))
-    else:
-        with_clause = None
-    return (query, clauseTables, decorator, join_tables, with_clause)
+    return (query, clauseTables, decorator, join_tables, with_clauses)
 
 
 def _process_order_by(params):
diff --git a/lib/lp/registry/browser/person.py b/lib/lp/registry/browser/person.py
index d14004b..4b4e920 100644
--- a/lib/lp/registry/browser/person.py
+++ b/lib/lp/registry/browser/person.py
@@ -2099,7 +2099,7 @@ class PersonParticipationView(LaunchpadView):
             # The member is a direct member; use the membership data.
             datejoined = membership.datejoined
             dateexpires = membership.dateexpires
-            if membership.person_id == team.teamownerID:
+            if membership.person_id == team.teamowner_id:
                 role = "Owner"
             elif membership.status == TeamMembershipStatus.ADMIN:
                 role = "Admin"
diff --git a/lib/lp/registry/doc/person-merge.rst b/lib/lp/registry/doc/person-merge.rst
index f50b89c..65d98e4 100644
--- a/lib/lp/registry/doc/person-merge.rst
+++ b/lib/lp/registry/doc/person-merge.rst
@@ -278,6 +278,7 @@ create, and then delete, the needed two people.
 
     >>> from lp.registry.model.person import PersonSet, Person
     >>> from lp.registry.interfaces.person import PersonCreationRationale
+    >>> from lp.services.database.interfaces import IStore
     >>> personset = PersonSet()
 
     >>> skip = []
@@ -312,11 +313,14 @@ create, and then delete, the needed two people.
     ...             display_name="Merge Winner",
     ...             creation_rationale=lp,
     ...         )
+    ...         IStore(Person).add(winner)
     ...         loser = Person(
     ...             name=name + ".loser",
     ...             display_name="Merge Loser",
     ...             creation_rationale=lp,
     ...         )
+    ...         IStore(Person).add(loser)
+    ...         IStore(Person).flush()
     ...         yield winner, loser
     ...
     >>> endless_supply_of_players = new_players()
diff --git a/lib/lp/registry/doc/vocabularies.rst b/lib/lp/registry/doc/vocabularies.rst
index ad6bf4b..ab7f5c5 100644
--- a/lib/lp/registry/doc/vocabularies.rst
+++ b/lib/lp/registry/doc/vocabularies.rst
@@ -611,7 +611,6 @@ Any person that's already merged is not part of this vocabulary:
 
     >>> naked_cprov = removeSecurityProxy(cprov)
     >>> naked_cprov.merged = 1
-    >>> naked_cprov.syncUpdate()
     >>> cprov in vocab
     False
 
diff --git a/lib/lp/registry/interfaces/person.py b/lib/lp/registry/interfaces/person.py
index fe77d12..4e66cfd 100644
--- a/lib/lp/registry/interfaces/person.py
+++ b/lib/lp/registry/interfaces/person.py
@@ -220,7 +220,7 @@ def validate_membership_policy(obj, attr, value):
         return None
 
     # If we are just creating a new team, it can have any membership policy.
-    if getattr(obj, "_SO_creating", True):
+    if getattr(obj, "_creating", True):
         return value
 
     team = obj
@@ -792,7 +792,7 @@ class IPersonLimitedView(IHasIcon, IHasLogo):
             "in listings of bugs or on a person's membership table."
         ),
     )
-    iconID = Int(title=_("Icon ID"), required=True, readonly=True)
+    icon_id = Int(title=_("Icon ID"), required=True, readonly=True)
     logo = exported(
         LogoImageUpload(
             title=_("Logo"),
@@ -806,7 +806,7 @@ class IPersonLimitedView(IHasIcon, IHasLogo):
             ),
         )
     )
-    logoID = Int(title=_("Logo ID"), required=True, readonly=True)
+    logo_id = Int(title=_("Logo ID"), required=True, readonly=True)
     # title is required for the Launchpad Page Layout main template
     title = Attribute("Person Page Title")
     is_probationary = exported(
@@ -890,7 +890,7 @@ class IPersonViewRestricted(
             ),
         )
     )
-    mugshotID = Int(title=_("Mugshot ID"), required=True, readonly=True)
+    mugshot_id = Int(title=_("Mugshot ID"), required=True, readonly=True)
 
     languages = exported(
         CollectionField(
@@ -1111,7 +1111,7 @@ class IPersonViewRestricted(
         ),
         exported_as="team_owner",
     )
-    teamownerID = Int(
+    teamowner_id = Int(
         title=_("The Team Owner's ID or None"), required=False, readonly=True
     )
     preferredemail = exported(
diff --git a/lib/lp/registry/model/distroseries.py b/lib/lp/registry/model/distroseries.py
index 22d2895..4119824 100644
--- a/lib/lp/registry/model/distroseries.py
+++ b/lib/lp/registry/model/distroseries.py
@@ -1580,7 +1580,7 @@ class DistroSeries(
             POTemplate.distroseries == self,
             POTemplate.iscurrent == True,
         )
-        contributors = contributors.order_by(*Person._storm_sortingColumns)
+        contributors = contributors.order_by(*Person._sortingColumns)
         contributors = contributors.config(distinct=True)
         return contributors
 
diff --git a/lib/lp/registry/model/person.py b/lib/lp/registry/model/person.py
index 81f4187..dcf118e 100644
--- a/lib/lp/registry/model/person.py
+++ b/lib/lp/registry/model/person.py
@@ -62,7 +62,7 @@ from storm.expr import (
     With,
 )
 from storm.info import ClassAlias
-from storm.locals import Int, Reference, ReferenceSet, Unicode
+from storm.locals import Bool, DateTime, Int, Reference, ReferenceSet, Unicode
 from storm.store import EmptyResultSet, Store
 from twisted.conch.ssh.common import getNS
 from twisted.conch.ssh.keys import Key
@@ -193,24 +193,16 @@ from lp.registry.model.teammembership import (
 )
 from lp.services.config import config
 from lp.services.database import bulk, postgresql
-from lp.services.database.constants import UTC_NOW
-from lp.services.database.datetimecol import UtcDateTimeCol
+from lp.services.database.constants import DEFAULT, UTC_NOW
 from lp.services.database.decoratedresultset import DecoratedResultSet
 from lp.services.database.enumcol import DBEnum
 from lp.services.database.interfaces import IStore
 from lp.services.database.policy import PrimaryDatabasePolicy
 from lp.services.database.sqlbase import (
-    SQLBase,
     convert_storm_clause_to_string,
     cursor,
     sqlvalues,
 )
-from lp.services.database.sqlobject import (
-    BoolCol,
-    ForeignKey,
-    IntCol,
-    StringCol,
-)
 from lp.services.database.stormbase import StormBase
 from lp.services.database.stormexpr import WithMaterialized, fti_search
 from lp.services.helpers import backslashreplace, shortlist
@@ -282,7 +274,7 @@ class TeamInvitationEvent:
         self.team = team
 
 
-class ValidPersonCache(SQLBase):
+class ValidPersonCache(StormBase):
     """Flags if a Person is active and usable in Launchpad.
 
     This is readonly, as this is a view in the database.
@@ -294,6 +286,10 @@ class ValidPersonCache(SQLBase):
     corroborating information.
     """
 
+    __storm_table__ = "ValidPersonCache"
+
+    id = Int(primary=True)
+
 
 def validate_person_visibility(person, attr, value):
     """Validate changes in visibility.
@@ -355,14 +351,14 @@ class PersonSettings(StormBase):
 
     __storm_table__ = "PersonSettings"
 
-    personID = Int("person", default=None, primary=True)
-    person = Reference(personID, "Person.id")
+    person_id = Int("person", default=None, primary=True)
+    person = Reference(person_id, "Person.id")
 
-    selfgenerated_bugnotifications = BoolCol(notNull=True, default=False)
+    selfgenerated_bugnotifications = Bool(allow_none=False, default=False)
 
-    expanded_notification_footers = BoolCol(notNull=False, default=False)
+    expanded_notification_footers = Bool(allow_none=True, default=False)
 
-    require_strong_email_authentication = BoolCol(notNull=False, default=False)
+    require_strong_email_authentication = Bool(allow_none=True, default=False)
 
 
 def readonly_settings(message, interface):
@@ -420,7 +416,7 @@ _readonly_person_settings = readonly_settings(
 @implementer(IPerson)
 @delegate_to(IPersonSettings, context="_person_settings")
 class Person(
-    SQLBase,
+    StormBase,
     HasBugsBase,
     HasSpecificationsMixin,
     HasTranslationImportsMixin,
@@ -431,14 +427,54 @@ class Person(
 ):
     """A Person."""
 
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        # Initialize our PersonSettings object/record.
+    __storm_table__ = "Person"
+
+    id = Int(primary=True)
+
+    _creating = False
+
+    def __init__(
+        self,
+        name,
+        display_name,
+        account=None,
+        teamowner=None,
+        description=None,
+        membership_policy=DEFAULT,
+        defaultrenewalperiod=None,
+        defaultmembershipperiod=None,
+        creation_rationale=None,
+        creation_comment=None,
+        registrant=None,
+        hide_email_addresses=False,
+    ):
+        super().__init__()
+        self._creating = True
+        self.name = name
+        self.display_name = display_name
+        self.account = account
+        self.teamowner = teamowner
+        self.description = description
+        self.membership_policy = membership_policy
+        self.defaultrenewalperiod = defaultrenewalperiod
+        self.defaultmembershipperiod = defaultmembershipperiod
+        self.creation_rationale = creation_rationale
+        self.creation_comment = creation_comment
+        self.registrant = registrant
+        self.hide_email_addresses = hide_email_addresses
         if not self.is_team:
-            # This is a Person, not a team.  Teams may want a TeamSettings
-            # in the future.
+            # Initialize our PersonSettings object/record.  This is a
+            # Person, not a team.  Teams may want a TeamSettings in the
+            # future.
             settings = PersonSettings()
             settings.person = self
+        self.__storm_loaded__()
+        del self._creating
+
+    def __storm_loaded__(self):
+        """Mark the person as a team when created or fetched from database."""
+        if self.is_team:
+            alsoProvides(self, ITeam)
 
     @cachedproperty
     def _person_settings(self):
@@ -462,13 +498,11 @@ class Person(
         return self.id
 
     sortingColumns = SQL("person_sort_key(Person.displayname, Person.name)")
-    # Redefine the default ordering into Storm syntax.
-    _storm_sortingColumns = ("Person.displayname", "Person.name")
     # When doing any sort of set operations (union, intersect, except_) with
-    # SQLObject we can't use sortingColumns because the table name Person is
-    # not available in that context, so we use this one.
+    # Storm we can't use sortingColumns because the table name Person is not
+    # available in that context, so we use this one.
     _sortingColumnsForSetOperations = SQL("person_sort_key(displayname, name)")
-    _defaultOrder = sortingColumns
+    __storm_order__ = sortingColumns
     _visibility_warning_cache_key = None
     _visibility_warning_cache = None
 
@@ -490,7 +524,7 @@ class Person(
         else:
             mailing_list = getUtility(IMailingListSet).get(self.name)
         can_rename = (
-            self._SO_creating
+            self._creating
             or not self.is_team
             or mailing_list is None
             or mailing_list.status == MailingListStatus.PURGED
@@ -499,35 +533,27 @@ class Person(
         # Everything's okay, so let SQLObject do the normal thing.
         return value
 
-    name = StringCol(
-        dbName="name",
-        alternateID=True,
-        notNull=True,
-        storm_validator=_validate_name,
-    )
+    name = Unicode(name="name", allow_none=False, validator=_validate_name)
 
     def __repr__(self):
         displayname = backslashreplace(self.displayname)
         return "<Person %s (%s)>" % (self.name, displayname)
 
-    display_name = StringCol(dbName="displayname", notNull=True)
+    display_name = Unicode(name="displayname", allow_none=False)
 
     @property
     def displayname(self):
         return self.display_name
 
-    teamdescription = StringCol(dbName="teamdescription", default=None)
-    homepage_content = StringCol(default=None)
-    _description = StringCol(dbName="description", default=None)
-    icon = ForeignKey(
-        dbName="icon", foreignKey="LibraryFileAlias", default=None
-    )
-    logo = ForeignKey(
-        dbName="logo", foreignKey="LibraryFileAlias", default=None
-    )
-    mugshot = ForeignKey(
-        dbName="mugshot", foreignKey="LibraryFileAlias", default=None
-    )
+    teamdescription = Unicode(name="teamdescription", default=None)
+    homepage_content = Unicode(default=None)
+    _description = Unicode(name="description", default=None)
+    icon_id = Int(name="icon", allow_none=True, default=None)
+    icon = Reference(icon_id, "LibraryFileAlias.id")
+    logo_id = Int(name="logo", allow_none=True, default=None)
+    logo = Reference(logo_id, "LibraryFileAlias.id")
+    mugshot_id = Int(name="mugshot", allow_none=True, default=None)
+    mugshot = Reference(mugshot_id, "LibraryFileAlias.id")
 
     @property
     def account_status(self):
@@ -546,12 +572,13 @@ class Person(
             raise NoAccountError()
         self.account.setStatus(status, user, comment)
 
-    teamowner = ForeignKey(
-        dbName="teamowner",
-        foreignKey="Person",
+    teamowner_id = Int(
+        name="teamowner",
+        validator=validate_public_person,
+        allow_none=True,
         default=None,
-        storm_validator=validate_public_person,
     )
+    teamowner = Reference(teamowner_id, "Person.id")
 
     sshkeys = ReferenceSet("id", "SSHKey.person_id")
 
@@ -565,30 +592,32 @@ class Person(
         default=TeamMembershipPolicy.RESTRICTED,
         validator=validate_membership_policy,
     )
-    defaultrenewalperiod = IntCol(dbName="defaultrenewalperiod", default=None)
-    defaultmembershipperiod = IntCol(
-        dbName="defaultmembershipperiod", default=None
-    )
+    defaultrenewalperiod = Int(name="defaultrenewalperiod", default=None)
+    defaultmembershipperiod = Int(name="defaultmembershipperiod", default=None)
     mailing_list_auto_subscribe_policy = DBEnum(
         enum=MailingListAutoSubscribePolicy,
         default=MailingListAutoSubscribePolicy.ON_REGISTRATION,
     )
 
-    merged = ForeignKey(dbName="merged", foreignKey="Person", default=None)
+    merged_id = Int(name="merged", allow_none=True, default=None)
+    merged = Reference(merged_id, "Person.id")
 
-    datecreated = UtcDateTimeCol(notNull=True, default=UTC_NOW)
+    datecreated = DateTime(
+        allow_none=False, default=UTC_NOW, tzinfo=timezone.utc
+    )
     creation_rationale = DBEnum(enum=PersonCreationRationale, default=None)
-    creation_comment = StringCol(default=None)
-    registrant = ForeignKey(
-        dbName="registrant",
-        foreignKey="Person",
+    creation_comment = Unicode(default=None)
+    registrant_id = Int(
+        name="registrant",
+        validator=validate_public_person,
+        allow_none=True,
         default=None,
-        storm_validator=validate_public_person,
     )
-    hide_email_addresses = BoolCol(notNull=True, default=False)
-    verbose_bugnotifications = BoolCol(notNull=True, default=True)
+    registrant = Reference(registrant_id, "Person.id")
+    hide_email_addresses = Bool(allow_none=False, default=False)
+    verbose_bugnotifications = Bool(allow_none=False, default=True)
 
-    signedcocs = ReferenceSet("<primary key>", "SignedCodeOfConduct.owner_id")
+    signedcocs = ReferenceSet("id", "SignedCodeOfConduct.owner_id")
     _ircnicknames = ReferenceSet("id", "IrcID.person_id")
     jabberids = ReferenceSet("id", "JabberID.person_id")
 
@@ -604,7 +633,7 @@ class Person(
         allow_none=False,
     )
 
-    personal_standing_reason = StringCol(default=None)
+    personal_standing_reason = Unicode(default=None)
 
     @property
     def description(self):
@@ -703,12 +732,6 @@ class Person(
         person_language.delete()
         self.deleteLanguagesCache()
 
-    def _init(self, *args, **kw):
-        """Mark the person as a team when created or fetched from database."""
-        SQLBase._init(self, *args, **kw)
-        if self.teamownerID is not None:
-            alsoProvides(self, ITeam)
-
     def convertToTeam(self, team_owner):
         """See `IPerson`."""
         if self.is_team:
@@ -1009,7 +1032,7 @@ class Person(
     @property
     def is_team(self):
         """See `IPerson`."""
-        return self.teamownerID is not None
+        return self.teamowner_id is not None
 
     @property
     def mailing_list(self):
@@ -1117,7 +1140,7 @@ class Person(
                     OR product.bug_supervisor = %(person)s
                 )
         """ % sqlvalues(
-            person=self
+            person=self.id
         )
 
         return "%s AND (%s)" % (
@@ -1157,7 +1180,7 @@ class Person(
                 ) _pillar
                 ON PillarName.name = _pillar.name
             """
-            % sqlvalues(person=self)
+            % sqlvalues(person=self.id)
         )
 
         results = IStore(self).using(SQL(origin)).find(find_spec)
@@ -1260,7 +1283,6 @@ class Person(
             CommercialSubscription,
         )
         from lp.registry.model.distribution import Distribution
-        from lp.registry.model.person import Person
         from lp.registry.model.product import Product
         from lp.registry.model.teammembership import TeamParticipation
 
@@ -1599,7 +1621,6 @@ class Person(
     def getAssignedSpecificationWorkItemsDueBefore(self, date, user):
         """See `IPerson`."""
         from lp.registry.model.distribution import Distribution
-        from lp.registry.model.person import Person
         from lp.registry.model.product import Product
 
         store = Store.of(self)
@@ -1811,7 +1832,7 @@ class Person(
             And(
                 TeamParticipation.team_id == self.id,
                 TeamParticipation.person_id != self.id,
-                Person.teamownerID != None,
+                IsNot(Person.teamowner_id, None),
             ),
             need_api=True,
         )
@@ -2061,7 +2082,7 @@ class Person(
                         Select(
                             Person.id,
                             tables=[Person],
-                            where=Person.teamownerID.is_in(team_select),
+                            where=Person.teamowner_id.is_in(team_select),
                         ),
                         Select(
                             TeamMembership.team_id,
@@ -2942,7 +2963,7 @@ class Person(
                 Person,
                 Person.id == TeamParticipation.team_id,
                 TeamParticipation.person == self,
-                IsNot(Person.teamownerID, None),
+                IsNot(Person.teamowner_id, None),
             )
             .order_by(Person.sortingColumns)
         )
@@ -2950,11 +2971,10 @@ class Person(
     @property
     def teams_indirectly_participated_in(self):
         """See `IPerson`."""
-        Team = ClassAlias(Person, "Team")
         store = Store.of(self)
         origin = [
-            Team,
-            Join(TeamParticipation, Team.id == TeamParticipation.team_id),
+            Person,
+            Join(TeamParticipation, Person.id == TeamParticipation.team_id),
             LeftJoin(
                 TeamMembership,
                 And(
@@ -2969,9 +2989,8 @@ class Person(
                 ),
             ),
         ]
-        find_objects = Team
         return store.using(*origin).find(
-            find_objects,
+            Person,
             And(
                 TeamParticipation.person == self.id,
                 TeamParticipation.person != TeamParticipation.team_id,
@@ -2988,8 +3007,8 @@ class Person(
                 Person,
                 Person.id == TeamParticipation.team_id,
                 TeamParticipation.person == self,
-                IsNot(Person.teamownerID, None),
-                IsNot(Person.iconID, None),
+                IsNot(Person.teamowner_id, None),
+                IsNot(Person.icon_id, None),
                 TeamParticipation.team != self,
             )
             .order_by(Person.sortingColumns)
@@ -4155,6 +4174,9 @@ class PersonSet:
             defaultrenewalperiod=defaultrenewalperiod,
             membership_policy=membership_policy,
         )
+        store = IStore(Person)
+        store.add(team)
+        store.flush()
         notify(ObjectCreatedEvent(team))
         # Here we add the owner as a team admin manually because we know what
         # we're doing (so we don't need to do any sanity checks) and we don't
@@ -4267,19 +4289,18 @@ class PersonSet:
         if not displayname:
             displayname = name.capitalize()
 
-        if account is None:
-            account_id = None
-        else:
-            account_id = account.id
         person = Person(
             name=name,
             display_name=displayname,
-            account_id=account_id,
+            account=account,
             creation_rationale=rationale,
             creation_comment=comment,
             hide_email_addresses=hide_email_addresses,
             registrant=registrant,
         )
+        store = IStore(Person)
+        store.add(person)
+        store.flush()
         return person
 
     def ensurePerson(
@@ -4309,7 +4330,7 @@ class PersonSet:
         """See `IPersonSet`."""
         clauses = [Person.name == name]
         if ignore_merged:
-            clauses.append(Is(Person.mergedID, None))
+            clauses.append(Is(Person.merged_id, None))
         return IStore(Person).find(Person, *clauses).one()
 
     def getByAccount(self, account):
@@ -4323,8 +4344,8 @@ class PersonSet:
             IStore(Person)
             .find(
                 Person,
-                Is(Person.teamownerID, None),
-                Is(Person.mergedID, None),
+                Is(Person.teamowner_id, None),
+                Is(Person.merged_id, None),
             )
             .count()
         )
@@ -4334,8 +4355,8 @@ class PersonSet:
             IStore(Person)
             .find(
                 Person,
-                IsNot(Person.teamownerID, None),
-                Is(Person.mergedID, None),
+                IsNot(Person.teamowner_id, None),
+                Is(Person.merged_id, None),
             )
             .count()
         )
@@ -4601,15 +4622,15 @@ class PersonSet:
         """See `IPersonSet`."""
         aliases = []
         aliases.extend(
-            person.iconID for person in people if person.iconID is not None
+            person.icon_id for person in people if person.icon_id is not None
         )
         aliases.extend(
-            person.logoID for person in people if person.logoID is not None
+            person.logo_id for person in people if person.logo_id is not None
         )
         aliases.extend(
-            person.mugshotID
+            person.mugshot_id
             for person in people
-            if person.mugshotID is not None
+            if person.mugshot_id is not None
         )
         if not aliases:
             return
@@ -4805,7 +4826,7 @@ class PersonSet:
 
         def preload_for_people(rows):
             if need_teamowner or need_api:
-                bulk.load(Person, [row[0].teamownerID for row in rows])
+                bulk.load(Person, [row[0].teamowner_id for row in rows])
 
         def prepopulate_person(row):
             result = row[0]
@@ -5559,7 +5580,7 @@ def _get_recipients_for_team(team):
                     EmailAddress.person != None,
                     Account.status == AccountStatus.ACTIVE,
                 ),
-                Person.teamownerID != None,
+                IsNot(Person.teamowner_id, None),
             ),
         ).config(distinct=True)
         next_ids = []
diff --git a/lib/lp/registry/personmerge.py b/lib/lp/registry/personmerge.py
index cec406c..2787bc2 100644
--- a/lib/lp/registry/personmerge.py
+++ b/lib/lp/registry/personmerge.py
@@ -1230,8 +1230,7 @@ def merge_people(from_person, to_person, reviewer, delete=False):
         cur.execute("SELECT id FROM Person WHERE name = %s" % sqlvalues(name))
         i += 1
     cur.execute(
-        "UPDATE Person SET name = %s WHERE id = %s"
-        % sqlvalues(name, from_person)
+        "UPDATE Person SET name = %s WHERE id = %s" % sqlvalues(name, from_id)
     )
 
     # Since we've updated the database behind Storm's back,
diff --git a/lib/lp/registry/scripts/closeaccount.py b/lib/lp/registry/scripts/closeaccount.py
index c7d4c55..7df78d0 100644
--- a/lib/lp/registry/scripts/closeaccount.py
+++ b/lib/lp/registry/scripts/closeaccount.py
@@ -236,7 +236,7 @@ def close_account(username, log):
     # Keep the corresponding PersonSettings row, but reset everything to the
     # defaults.
     table_notification("PersonSettings")
-    store.find(PersonSettings, PersonSettings.personID == person.id).set(
+    store.find(PersonSettings, PersonSettings.person == person).set(
         selfgenerated_bugnotifications=DEFAULT,
         # XXX cjwatson 2018-11-29: These two columns have NULL defaults, but
         # perhaps shouldn't?
diff --git a/lib/lp/registry/vocabularies.py b/lib/lp/registry/vocabularies.py
index 56453ca..0104a33 100644
--- a/lib/lp/registry/vocabularies.py
+++ b/lib/lp/registry/vocabularies.py
@@ -71,6 +71,8 @@ from storm.expr import (
     And,
     Column,
     Desc,
+    Is,
+    IsNot,
     Join,
     LeftJoin,
     Not,
@@ -176,7 +178,6 @@ from lp.services.webapp.vocabulary import (
     IHugeVocabulary,
     NamedStormHugeVocabulary,
     NamedStormVocabulary,
-    SQLObjectVocabularyBase,
     StormVocabularyBase,
     VocabularyFilter,
 )
@@ -208,7 +209,6 @@ class BasePersonVocabulary:
         If the token contains an '@', treat it like an email. Otherwise,
         treat it like a name.
         """
-        token = six.ensure_text(token)
         if "@" in token:
             # This looks like an email token, so let's do an object
             # lookup based on that.
@@ -369,11 +369,11 @@ def project_products_vocabulary_factory(context):
     )
 
 
-class UserTeamsParticipationVocabulary(SQLObjectVocabularyBase):
+class UserTeamsParticipationVocabulary(StormVocabularyBase):
     """Describes the public teams in which the current user participates."""
 
     _table = Person
-    _orderBy = "display_name"
+    _order_by = "display_name"
 
     INCLUDE_PRIVATE_TEAM = False
 
@@ -401,7 +401,7 @@ class UserTeamsParticipationVocabulary(SQLObjectVocabularyBase):
             teams = list(
                 IStore(Person)
                 .find(Person, *clauses)
-                .order_by(Person._storm_sortingColumns)
+                .order_by(Person._sortingColumns)
             )
             # Users can view all the teams they belong to.
             precache_permission_for_objects(
@@ -428,7 +428,7 @@ class UserTeamsParticipationVocabulary(SQLObjectVocabularyBase):
 
 @implementer(IHugeVocabulary)
 class NonMergedPeopleAndTeamsVocabulary(
-    BasePersonVocabulary, SQLObjectVocabularyBase
+    BasePersonVocabulary, StormVocabularyBase
 ):
     """The set of all non-merged people and teams.
 
@@ -437,7 +437,7 @@ class NonMergedPeopleAndTeamsVocabulary(
     a preferred email address, that is, unvalidated person profiles.
     """
 
-    _orderBy = ["display_name"]
+    _order_by = ["display_name"]
     displayname = "Select a Person or Team"
     step_title = "Search"
 
@@ -449,7 +449,7 @@ class NonMergedPeopleAndTeamsVocabulary(
         return getUtility(IPersonSet).find(text)
 
     def search(self, text, vocab_filter=None):
-        """See `SQLObjectVocabularyBase`.
+        """See `StormVocabularyBase`.
 
         Return people/teams whose fti or email address match :text.
         """
@@ -461,7 +461,7 @@ class NonMergedPeopleAndTeamsVocabulary(
 
 @implementer(IHugeVocabulary)
 class PersonAccountToMergeVocabulary(
-    BasePersonVocabulary, SQLObjectVocabularyBase
+    BasePersonVocabulary, StormVocabularyBase
 ):
     """The set of all non-merged people with at least one email address.
 
@@ -469,7 +469,7 @@ class PersonAccountToMergeVocabulary(
     accounts to merge. You *don't* want to use it.
     """
 
-    _orderBy = ["display_name"]
+    _order_by = ["display_name"]
     displayname = "Select a Person to Merge"
     step_title = "Search"
     must_have_email = True
@@ -486,7 +486,7 @@ class PersonAccountToMergeVocabulary(
         )
 
     def search(self, text, vocab_filter=None):
-        """See `SQLObjectVocabularyBase`.
+        """See `StormVocabularyBase`.
 
         Return people whose fti or email address match :text.
         """
@@ -516,7 +516,7 @@ class VocabularyFilterPerson(VocabularyFilter):
 
     @property
     def filter_terms(self):
-        return [Person.teamownerID == None]
+        return [Is(Person.teamowner_id, None)]
 
 
 class VocabularyFilterTeam(VocabularyFilter):
@@ -529,13 +529,11 @@ class VocabularyFilterTeam(VocabularyFilter):
 
     @property
     def filter_terms(self):
-        return [Person.teamownerID != None]
+        return [IsNot(Person.teamowner_id, None)]
 
 
 @implementer(IHugeVocabulary)
-class ValidPersonOrTeamVocabulary(
-    BasePersonVocabulary, SQLObjectVocabularyBase
-):
+class ValidPersonOrTeamVocabulary(BasePersonVocabulary, StormVocabularyBase):
     """The set of valid, viewable Persons/Teams in Launchpad.
 
     A Person is considered valid if they have a preferred email address, and
diff --git a/lib/lp/translations/model/poexportrequest.py b/lib/lp/translations/model/poexportrequest.py
index 3313457..9dd9ec5 100644
--- a/lib/lp/translations/model/poexportrequest.py
+++ b/lib/lp/translations/model/poexportrequest.py
@@ -92,7 +92,7 @@ class POExportRequestSet:
         )
 
         query_params = {
-            "person": quote(person),
+            "person": quote(person.id),
             "format": quote(format),
             "templates": potemplate_ids,
             "pofiles": pofile_ids,
diff --git a/lib/lp/translations/model/pofile.py b/lib/lp/translations/model/pofile.py
index 14626b1..623fb69 100644
--- a/lib/lp/translations/model/pofile.py
+++ b/lib/lp/translations/model/pofile.py
@@ -456,7 +456,7 @@ class POFile(StormBase, POFileMixIn):
             )
             .config(distinct=True)
         )
-        contributors = contributors.order_by(*Person._storm_sortingColumns)
+        contributors = contributors.order_by(*Person._sortingColumns)
         contributors = contributors.config(distinct=True)
         return contributors
 
diff --git a/lib/lp/translations/model/translationgroup.py b/lib/lp/translations/model/translationgroup.py
index 943b38e..c8dc2dd 100644
--- a/lib/lp/translations/model/translationgroup.py
+++ b/lib/lp/translations/model/translationgroup.py
@@ -175,7 +175,7 @@ class TranslationGroup(StormBase):
             Translator,
             Language,
             Person,
-            LeftJoin(LibraryFileAlias, LibraryFileAlias.id == Person.iconID),
+            LeftJoin(LibraryFileAlias, LibraryFileAlias.id == Person.icon_id),
             LeftJoin(
                 LibraryFileContent,
                 LibraryFileContent.id == LibraryFileAlias.contentID,
diff --git a/lib/lp/translations/scripts/remove_translations.py b/lib/lp/translations/scripts/remove_translations.py
index c042b8a..e854640 100644
--- a/lib/lp/translations/scripts/remove_translations.py
+++ b/lib/lp/translations/scripts/remove_translations.py
@@ -444,11 +444,11 @@ def remove_translations(
     conditions = set()
     if submitter is not None:
         conditions.add(
-            "TranslationMessage.submitter = %s" % sqlvalues(submitter)
+            "TranslationMessage.submitter = %s" % sqlvalues(submitter.id)
         )
     if reviewer is not None:
         conditions.add(
-            "TranslationMessage.reviewer = %s" % sqlvalues(reviewer)
+            "TranslationMessage.reviewer = %s" % sqlvalues(reviewer.id)
         )
     if date_created is not None:
         conditions.add(