← Back to team overview

launchpad-reviewers team mailing list archive

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

 

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

Commit message:
Convert Branch to Storm

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/447535
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:stormify-branch into launchpad:master.
diff --git a/lib/lp/app/doc/tales.rst b/lib/lp/app/doc/tales.rst
index e872cff..662c1ff 100644
--- a/lib/lp/app/doc/tales.rst
+++ b/lib/lp/app/doc/tales.rst
@@ -597,7 +597,7 @@ For branches, fmt:link links to the branch page.
     >>> eric = factory.makePerson(name="eric")
     >>> fooix = factory.makeProduct(name="fooix")
     >>> branch = factory.makeProductBranch(
-    ...     owner=eric, product=fooix, name="bar", title="The branch title"
+    ...     owner=eric, product=fooix, name="bar"
     ... )
     >>> print(test_tales("branch/fmt:link", branch=branch))
     <a href=".../~eric/fooix/bar"
@@ -680,7 +680,7 @@ Branch subscriptions show the person and branch name.  For users without
 adequate permissions, a link is not generated.
 
     >>> branch = factory.makeProductBranch(
-    ...     owner=eric, product=fooix, name="my-branch", title="My Branch"
+    ...     owner=eric, product=fooix, name="my-branch"
     ... )
     >>> michael = factory.makePerson(
     ...     name="michael", displayname="Michael the Viking"
diff --git a/lib/lp/code/doc/branch.rst b/lib/lp/code/doc/branch.rst
index 7984f04..1b92425 100644
--- a/lib/lp/code/doc/branch.rst
+++ b/lib/lp/code/doc/branch.rst
@@ -273,7 +273,7 @@ Branch names
 
 Branches have a display name that is the bzr_identity.
 
-    >>> untitled_branch = factory.makeAnyBranch(title=None)
+    >>> untitled_branch = factory.makeAnyBranch()
     >>> untitled_branch.displayname == untitled_branch.bzr_identity
     True
 
diff --git a/lib/lp/code/interfaces/branchnamespace.py b/lib/lp/code/interfaces/branchnamespace.py
index 345f36d..24c6ffb 100644
--- a/lib/lp/code/interfaces/branchnamespace.py
+++ b/lib/lp/code/interfaces/branchnamespace.py
@@ -32,9 +32,8 @@ class IBranchNamespace(Interface):
         name,
         registrant,
         url=None,
-        title=None,
         lifecycle_status=BranchLifecycleStatus.DEVELOPMENT,
-        summary=None,
+        description=None,
         whiteboard=None,
     ):
         """Create and return an `IBranch` in this namespace."""
diff --git a/lib/lp/code/mail/tests/test_branch.py b/lib/lp/code/mail/tests/test_branch.py
index e1faf88..c9285fc 100644
--- a/lib/lp/code/mail/tests/test_branch.py
+++ b/lib/lp/code/mail/tests/test_branch.py
@@ -149,9 +149,9 @@ class TestRecipientReasonBzr(TestRecipientReasonMixin, TestCaseWithFactory):
         """Test fixture."""
         if subscriber is None:
             subscriber = self.factory.makePerson()
-        source_branch = self.factory.makeProductBranch(title="foo")
+        source_branch = self.factory.makeProductBranch()
         target_branch = self.factory.makeProductBranch(
-            product=source_branch.product, title="bar"
+            product=source_branch.product
         )
         merge_proposal = source_branch.addLandingTarget(
             source_branch.owner, target_branch
diff --git a/lib/lp/code/model/branch.py b/lib/lp/code/model/branch.py
index b9f9b5f..48fd5fc 100644
--- a/lib/lp/code/model/branch.py
+++ b/lib/lp/code/model/branch.py
@@ -16,7 +16,14 @@ from breezy.revision import NULL_REVISION
 from breezy.url_policy_open import open_only_scheme
 from lazr.lifecycle.event import ObjectCreatedEvent
 from storm.expr import SQL, And, Coalesce, Desc, Join, Not, Or, Select
-from storm.locals import AutoReload, ReferenceSet
+from storm.locals import (
+    AutoReload,
+    DateTime,
+    Int,
+    Reference,
+    ReferenceSet,
+    Unicode,
+)
 from storm.store import Store
 from zope.component import getUtility
 from zope.event import notify
@@ -130,12 +137,10 @@ from lp.registry.model.teammembership import TeamParticipation
 from lp.services.config import config
 from lp.services.database import bulk
 from lp.services.database.constants import DEFAULT, UTC_NOW
-from lp.services.database.datetimecol import UtcDateTimeCol
 from lp.services.database.decoratedresultset import DecoratedResultSet
 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 ForeignKey, IntCol, StringCol
+from lp.services.database.stormbase import StormBase
 from lp.services.database.stormexpr import Array, ArrayAgg, ArrayIntersects
 from lp.services.helpers import shortlist
 from lp.services.job.interfaces.job import JobStatus
@@ -151,27 +156,68 @@ from lp.snappy.interfaces.snap import ISnapSet
 
 
 @implementer(IBranch, IInformationType)
-class Branch(SQLBase, WebhookTargetMixin, BzrIdentityMixin):
+class Branch(StormBase, WebhookTargetMixin, BzrIdentityMixin):
     """A sequence of ordered revisions in Bazaar."""
 
-    _table = "Branch"
+    __storm_table__ = "Branch"
+
+    id = Int(primary=True)
 
     branch_type = DBEnum(enum=BranchType, allow_none=False)
 
-    name = StringCol(notNull=False)
-    url = StringCol(dbName="url")
-    description = StringCol(dbName="summary")
+    name = Unicode(allow_none=False)
+    url = Unicode(name="url")
+    description = Unicode(name="summary")
     branch_format = DBEnum(enum=BranchFormat)
     repository_format = DBEnum(enum=RepositoryFormat)
     # XXX: Aaron Bentley 2008-06-13
     # Rename the metadir_format in the database, see bug 239746
     control_format = DBEnum(enum=ControlFormat, name="metadir_format")
-    whiteboard = StringCol(default=None)
-    mirror_status_message = StringCol(default=None)
+    whiteboard = Unicode(default=None)
+    mirror_status_message = Unicode(default=None)
     information_type = DBEnum(
-        enum=InformationType, default=InformationType.PUBLIC
+        enum=InformationType, allow_none=False, default=InformationType.PUBLIC
     )
 
+    def __init__(
+        self,
+        branch_type,
+        name,
+        registrant,
+        owner,
+        url=None,
+        description=None,
+        branch_format=None,
+        repository_format=None,
+        control_format=None,
+        whiteboard=None,
+        information_type=InformationType.PUBLIC,
+        product=None,
+        sourcepackage=None,
+        lifecycle_status=BranchLifecycleStatus.DEVELOPMENT,
+        date_created=DEFAULT,
+        date_last_modified=DEFAULT,
+    ):
+        super().__init__()
+        self.branch_type = branch_type
+        self.name = name
+        self.registrant = registrant
+        self.owner = owner
+        self.url = url
+        self.description = description
+        self.branch_format = branch_format
+        self.repository_format = repository_format
+        self.control_format = control_format
+        self.whiteboard = whiteboard
+        self.information_type = information_type
+        self.product = product
+        if sourcepackage is not None:
+            self.distroseries = sourcepackage.distroseries
+            self.sourcepackagename = sourcepackage.sourcepackagename
+        self.lifecycle_status = lifecycle_status
+        self.date_created = date_created
+        self.date_last_modified = date_last_modified
+
     @property
     def valid_webhook_event_types(self):
         return ["bzr:push:0.1", "merge-proposal:0.1"]
@@ -278,41 +324,28 @@ class Branch(SQLBase, WebhookTargetMixin, BzrIdentityMixin):
         # such subscriptions.
         getUtility(IRemoveArtifactSubscriptionsJobSource).create(who, [self])
 
-    registrant = ForeignKey(
-        dbName="registrant",
-        foreignKey="Person",
-        storm_validator=validate_public_person,
-        notNull=True,
-    )
-    owner = ForeignKey(
-        dbName="owner",
-        foreignKey="Person",
-        storm_validator=validate_person,
-        notNull=True,
+    registrant_id = Int(
+        name="registrant", validator=validate_public_person, allow_none=False
     )
+    registrant = Reference(registrant_id, "Person.id")
+    owner_id = Int(name="owner", validator=validate_person, allow_none=False)
+    owner = Reference(owner_id, "Person.id")
 
     def setOwner(self, new_owner, user):
         """See `IBranch`."""
         new_namespace = self.target.getNamespace(new_owner)
         new_namespace.moveBranch(self, user, rename_if_necessary=True)
 
-    reviewer = ForeignKey(
-        dbName="reviewer",
-        foreignKey="Person",
-        storm_validator=validate_person,
-        default=None,
-    )
+    reviewer_id = Int(name="reviewer", validator=validate_person, default=None)
+    reviewer = Reference(reviewer_id, "Person.id")
 
-    product = ForeignKey(dbName="product", foreignKey="Product", default=None)
+    product_id = Int(name="product", default=None)
+    product = Reference(product_id, "Product.id")
 
-    distroseries = ForeignKey(
-        dbName="distroseries", foreignKey="DistroSeries", default=None
-    )
-    sourcepackagename = ForeignKey(
-        dbName="sourcepackagename",
-        foreignKey="SourcePackageName",
-        default=None,
-    )
+    distroseries_id = Int(name="distroseries", default=None)
+    distroseries = Reference(distroseries_id, "DistroSeries.id")
+    sourcepackagename_id = Int(name="sourcepackagename", default=None)
+    sourcepackagename = Reference(sourcepackagename_id, "SourcePackageName.id")
 
     lifecycle_status = DBEnum(
         enum=BranchLifecycleStatus,
@@ -320,24 +353,23 @@ class Branch(SQLBase, WebhookTargetMixin, BzrIdentityMixin):
         default=BranchLifecycleStatus.DEVELOPMENT,
     )
 
-    last_mirrored = UtcDateTimeCol(default=None)
-    last_mirrored_id = StringCol(default=None)
-    last_mirror_attempt = UtcDateTimeCol(default=None)
-    mirror_failures = IntCol(default=0, notNull=True)
-    next_mirror_time = UtcDateTimeCol(default=None)
-
-    last_scanned = UtcDateTimeCol(default=None)
-    last_scanned_id = StringCol(default=None)
-    revision_count = IntCol(default=DEFAULT, notNull=True)
-    stacked_on = ForeignKey(
-        dbName="stacked_on", foreignKey="Branch", default=None
-    )
+    last_mirrored = DateTime(default=None, tzinfo=timezone.utc)
+    last_mirrored_id = Unicode(default=None)
+    last_mirror_attempt = DateTime(default=None, tzinfo=timezone.utc)
+    mirror_failures = Int(default=0, allow_none=False)
+    next_mirror_time = DateTime(default=None, tzinfo=timezone.utc)
+
+    last_scanned = DateTime(default=None, tzinfo=timezone.utc)
+    last_scanned_id = Unicode(default=None)
+    revision_count = Int(default=DEFAULT, allow_none=False)
+    stacked_on_id = Int(name="stacked_on", default=None)
+    stacked_on = Reference(stacked_on_id, "Branch.id")
 
     # The unique_name is maintined by a SQL trigger.
-    unique_name = StringCol()
+    unique_name = Unicode()
     # Denormalised columns used primarily for sorting.
-    owner_name = StringCol()
-    target_suffix = StringCol()
+    owner_name = Unicode()
+    target_suffix = Unicode()
 
     def __repr__(self):
         return "<Branch %r (%d)>" % (self.unique_name, self.id)
@@ -500,8 +532,12 @@ class Branch(SQLBase, WebhookTargetMixin, BzrIdentityMixin):
         """See `IBranch`."""
         return spec.unlinkBranch(self, user)
 
-    date_created = UtcDateTimeCol(notNull=True, default=DEFAULT)
-    date_last_modified = UtcDateTimeCol(notNull=True, default=DEFAULT)
+    date_created = DateTime(
+        allow_none=False, default=DEFAULT, tzinfo=timezone.utc
+    )
+    date_last_modified = DateTime(
+        allow_none=False, default=DEFAULT, tzinfo=timezone.utc
+    )
 
     @property
     def landing_targets(self):
@@ -1642,7 +1678,7 @@ class Branch(SQLBase, WebhookTargetMixin, BzrIdentityMixin):
 
         # Now destroy the branch.
         branch_id = self.id
-        SQLBase.destroySelf(self)
+        Store.of(self).remove(self)
         # And now create a job to remove the branch from disk when it's done.
         job = getUtility(IReclaimBranchSpaceJobSource).create(branch_id)
         job.celeryRunOnCommit()
diff --git a/lib/lp/code/model/branchcollection.py b/lib/lp/code/model/branchcollection.py
index 55cbb0c..fd5677d 100644
--- a/lib/lp/code/model/branchcollection.py
+++ b/lib/lp/code/model/branchcollection.py
@@ -127,7 +127,7 @@ class GenericBranchCollection:
     def ownerCounts(self):
         """See `IBranchCollection`."""
         is_team = Person.teamowner != None
-        branch_owners = self._getBranchSelect((Branch.ownerID,))
+        branch_owners = self._getBranchSelect((Branch.owner_id,))
         counts = dict(
             self.store.find(
                 (is_team, Count(Person.id)), Person.id.is_in(branch_owners)
@@ -245,9 +245,9 @@ class GenericBranchCollection:
     def preloadDataForBranches(branches):
         """Preload branches' cached associated targets, product series, and
         suite source packages."""
-        load_related(SourcePackageName, branches, ["sourcepackagenameID"])
-        load_related(DistroSeries, branches, ["distroseriesID"])
-        load_related(Product, branches, ["productID"])
+        load_related(SourcePackageName, branches, ["sourcepackagename_id"])
+        load_related(DistroSeries, branches, ["distroseries_id"])
+        load_related(Product, branches, ["product_id"])
         caches = {branch.id: get_property_cache(branch) for branch in branches}
         branch_ids = caches.keys()
         for cache in caches.values():
@@ -259,9 +259,9 @@ class GenericBranchCollection:
         from lp.registry.model.productseries import ProductSeries
 
         for productseries in IStore(ProductSeries).find(
-            ProductSeries, ProductSeries.branchID.is_in(branch_ids)
+            ProductSeries, ProductSeries.branch_id.is_in(branch_ids)
         ):
-            cache = caches[productseries.branchID]
+            cache = caches[productseries.branch_id]
             cache._associatedProductSeries.append(productseries)
         # associatedSuiteSourcePackages
         series_set = getUtility(IFindOfficialBranchLinks)
@@ -346,7 +346,7 @@ class GenericBranchCollection:
             # So far have only needed the persons for their canonical_url - no
             # need for validity etc in the /branches API call.
             load_related(
-                Person, rows, ["ownerID", "registrantID", "reviewerID"]
+                Person, rows, ["owner_id", "registrant_id", "reviewer_id"]
             )
             load_referencing(BugBranch, rows, ["branch_id"])
 
@@ -667,7 +667,7 @@ class GenericBranchCollection:
         # BranchCollection conceptual model, but we're not quite sure how to
         # fix it just yet.  Perhaps when bug 337494 is fixed, we'd be able to
         # sensibly be able to move this method to another utility class.
-        branch_query = self._getBranchSelect((Branch.ownerID,))
+        branch_query = self._getBranchSelect((Branch.owner_id,))
         return self.store.find(
             Person,
             Person.id == TeamParticipation.teamID,
@@ -754,7 +754,7 @@ class GenericBranchCollection:
         return self._filterBy(
             [Person.membership_policy.is_in(EXCLUSIVE_TEAM_POLICY)],
             table=Person,
-            join=Join(Person, Branch.ownerID == Person.id),
+            join=Join(Person, Branch.owner_id == Person.id),
         )
 
     def isSeries(self):
@@ -763,9 +763,9 @@ class GenericBranchCollection:
         from lp.registry.model.productseries import ProductSeries
 
         return self._filterBy(
-            [Branch.id == ProductSeries.branchID],
+            [Branch.id == ProductSeries.branch_id],
             table=ProductSeries,
-            join=Join(ProductSeries, Branch.id == ProductSeries.branchID),
+            join=Join(ProductSeries, Branch.id == ProductSeries.branch_id),
         )
 
     def ownedBy(self, person):
@@ -778,7 +778,7 @@ class GenericBranchCollection:
             TeamParticipation.teamID,
             where=TeamParticipation.personID == person.id,
         )
-        return self._filterBy([In(Branch.ownerID, subquery)], symmetric=False)
+        return self._filterBy([In(Branch.owner_id, subquery)], symmetric=False)
 
     def registeredBy(self, person):
         """See `IBranchCollection`."""
diff --git a/lib/lp/code/model/branchlistingqueryoptimiser.py b/lib/lp/code/model/branchlistingqueryoptimiser.py
index a108d14..6dc8ee2 100644
--- a/lib/lp/code/model/branchlistingqueryoptimiser.py
+++ b/lib/lp/code/model/branchlistingqueryoptimiser.py
@@ -38,7 +38,7 @@ class BranchListingQueryOptimiser:
             for product, series in IStore(Product)
             .find(
                 (Product, ProductSeries),
-                ProductSeries.branchID.is_in(branch_ids),
+                ProductSeries.branch_id.is_in(branch_ids),
                 ProductSeries.product == Product.id,
             )
             .order_by(ProductSeries.name)
diff --git a/lib/lp/code/model/branchlookup.py b/lib/lp/code/model/branchlookup.py
index 5c40120..5abf549 100644
--- a/lib/lp/code/model/branchlookup.py
+++ b/lib/lp/code/model/branchlookup.py
@@ -50,7 +50,6 @@ from lp.registry.model.product import Product
 from lp.registry.model.sourcepackagename import SourcePackageName
 from lp.services.config import config
 from lp.services.database.interfaces import IStore
-from lp.services.database.sqlobject import SQLObjectNotFound
 from lp.services.webapp.authorization import check_permission
 
 
@@ -189,10 +188,10 @@ class BranchLookup:
 
     def get(self, branch_id, default=None):
         """See `IBranchLookup`."""
-        try:
-            return Branch.get(branch_id)
-        except SQLObjectNotFound:
+        branch = IStore(Branch).get(Branch, branch_id)
+        if branch is None:
             return default
+        return branch
 
     @staticmethod
     def uriToHostingPath(uri):
@@ -233,7 +232,7 @@ class BranchLookup:
                 return None
             return self.getByPath(uri.path.lstrip("/"))
 
-        return Branch.selectOneBy(url=url)
+        return IStore(Branch).find(Branch, url=url).one()
 
     def performLookup(self, lookup):
         if lookup["type"] == "id":
@@ -365,7 +364,7 @@ class BranchLookup:
             .find(
                 Branch,
                 Person.name == owner,
-                Branch.distroseriesID
+                Branch.distroseries_id
                 == Select(
                     DistroSeries.id,
                     And(
diff --git a/lib/lp/code/model/branchmergeproposal.py b/lib/lp/code/model/branchmergeproposal.py
index 04b28b3..924658c 100644
--- a/lib/lp/code/model/branchmergeproposal.py
+++ b/lib/lp/code/model/branchmergeproposal.py
@@ -1630,7 +1630,7 @@ class BranchMergeProposal(StormBase, BugLinkTargetMixin):
         # persons.  We need the target repository owner as well; unlike
         # branches, repository unique names aren't trigger-maintained.
         person_ids.update(
-            branch.ownerID
+            branch.owner_id
             for branch in branches
             if branch.id in source_branch_ids
         )
diff --git a/lib/lp/code/model/branchnamespace.py b/lib/lp/code/model/branchnamespace.py
index ca997a5..f441c41 100644
--- a/lib/lp/code/model/branchnamespace.py
+++ b/lib/lp/code/model/branchnamespace.py
@@ -62,7 +62,7 @@ from lp.registry.interfaces.product import IProduct, IProductSet, NoSuchProduct
 from lp.registry.interfaces.projectgroup import IProjectGroup
 from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet
 from lp.registry.model.sourcepackage import SourcePackage
-from lp.services.database.constants import UTC_NOW
+from lp.services.database.constants import DEFAULT
 from lp.services.database.interfaces import IStore
 
 BRANCH_POLICY_ALLOWED_TYPES = {
@@ -107,11 +107,10 @@ class _BaseBranchNamespace:
         name,
         registrant,
         url=None,
-        title=None,
         lifecycle_status=BranchLifecycleStatus.DEVELOPMENT,
-        summary=None,
+        description=None,
         whiteboard=None,
-        date_created=None,
+        date_created=DEFAULT,
         branch_format=None,
         repository_format=None,
         control_format=None,
@@ -121,21 +120,12 @@ class _BaseBranchNamespace:
         self.validateRegistrant(registrant)
         self.validateBranchName(name)
 
-        if date_created is None:
-            date_created = UTC_NOW
-
         # Run any necessary data massage on the branch URL.
         if url is not None:
             url = IBranch["url"].normalize(url)
 
         product = getattr(self, "product", None)
         sourcepackage = getattr(self, "sourcepackage", None)
-        if sourcepackage is None:
-            distroseries = None
-            sourcepackagename = None
-        else:
-            distroseries = sourcepackage.distroseries
-            sourcepackagename = sourcepackage.sourcepackagename
 
         information_type = self.getDefaultInformationType(registrant)
         if information_type is None:
@@ -146,10 +136,10 @@ class _BaseBranchNamespace:
             name=name,
             owner=self.owner,
             product=product,
+            sourcepackage=sourcepackage,
             url=url,
-            title=title,
             lifecycle_status=lifecycle_status,
-            summary=summary,
+            description=description,
             whiteboard=whiteboard,
             information_type=information_type,
             date_created=date_created,
@@ -158,8 +148,6 @@ class _BaseBranchNamespace:
             branch_format=branch_format,
             repository_format=repository_format,
             control_format=control_format,
-            distroseries=distroseries,
-            sourcepackagename=sourcepackagename,
         )
         branch._reconcileAccess()
 
diff --git a/lib/lp/code/model/revision.py b/lib/lp/code/model/revision.py
index 8b3f793..c3416db 100644
--- a/lib/lp/code/model/revision.py
+++ b/lib/lp/code/model/revision.py
@@ -184,7 +184,7 @@ class Revision(StormBase):
             result_set.order_by(Asc(BranchRevision.sequence))
         else:
             result_set.order_by(
-                Branch.ownerID != self.revision_author.person_id,
+                Branch.owner_id != self.revision_author.person_id,
                 Asc(BranchRevision.sequence),
             )
 
@@ -639,8 +639,8 @@ class RevisionSet:
             insert_columns.append("NULL")
             subselect_clauses.append("product IS NULL")
         else:
-            insert_columns.append(str(naked_branch.productID))
-            subselect_clauses.append("product = %s" % naked_branch.productID)
+            insert_columns.append(str(naked_branch.product_id))
+            subselect_clauses.append("product = %s" % naked_branch.product_id)
 
         if branch.distroseries is None:
             insert_columns.extend(["NULL", "NULL"])
@@ -650,15 +650,15 @@ class RevisionSet:
         else:
             insert_columns.extend(
                 [
-                    str(naked_branch.distroseriesID),
-                    str(naked_branch.sourcepackagenameID),
+                    str(naked_branch.distroseries_id),
+                    str(naked_branch.sourcepackagename_id),
                 ]
             )
             subselect_clauses.extend(
                 [
-                    "distroseries = %s" % naked_branch.distroseriesID,
+                    "distroseries = %s" % naked_branch.distroseries_id,
                     "sourcepackagename = %s"
-                    % naked_branch.sourcepackagenameID,
+                    % naked_branch.sourcepackagename_id,
                 ]
             )
 
diff --git a/lib/lp/code/model/tests/test_branchnamespace.py b/lib/lp/code/model/tests/test_branchnamespace.py
index 1e6f3e6..20bbf32 100644
--- a/lib/lp/code/model/tests/test_branchnamespace.py
+++ b/lib/lp/code/model/tests/test_branchnamespace.py
@@ -94,17 +94,15 @@ class NamespaceMixin:
         namespace = self.getNamespace()
         branch_name = self.factory.getUniqueString()
         registrant = removeSecurityProxy(namespace).owner
-        title = self.factory.getUniqueString()
-        summary = self.factory.getUniqueString()
+        description = self.factory.getUniqueString()
         whiteboard = self.factory.getUniqueString()
         branch = namespace.createBranch(
             BranchType.HOSTED,
             branch_name,
             registrant,
             url=None,
-            title=title,
             lifecycle_status=BranchLifecycleStatus.EXPERIMENTAL,
-            summary=summary,
+            description=description,
             whiteboard=whiteboard,
         )
         self.assertEqual(BranchType.HOSTED, branch.branch_type)
diff --git a/lib/lp/code/model/tests/test_codereviewcomment.py b/lib/lp/code/model/tests/test_codereviewcomment.py
index 823e4f5..3c01ea9 100644
--- a/lib/lp/code/model/tests/test_codereviewcomment.py
+++ b/lib/lp/code/model/tests/test_codereviewcomment.py
@@ -20,10 +20,8 @@ class TestCodeReviewComment(TestCaseWithFactory):
 
     def setUp(self):
         TestCaseWithFactory.setUp(self, "admin@xxxxxxxxxxxxx")
-        source = self.factory.makeProductBranch(title="source-branch")
-        target = self.factory.makeProductBranch(
-            product=source.product, title="target-branch"
-        )
+        source = self.factory.makeProductBranch()
+        target = self.factory.makeProductBranch(product=source.product)
         self.bmp = source.addLandingTarget(source.owner, target)
         self.submitter = self.factory.makePerson()
         self.reviewer = self.factory.makePerson()
diff --git a/lib/lp/code/stories/branches/xx-branch-index.rst b/lib/lp/code/stories/branches/xx-branch-index.rst
index e2946b1..bdab297 100644
--- a/lib/lp/code/stories/branches/xx-branch-index.rst
+++ b/lib/lp/code/stories/branches/xx-branch-index.rst
@@ -235,7 +235,6 @@ it has been mirrored:
     ...     name="mirrored",
     ...     owner=no_priv,
     ...     url="http://example.com/mirrored";,
-    ...     title="Disabled branch",
     ... )
     >>> branch.last_mirrored = datetime(
     ...     year=2007, month=10, day=1, tzinfo=timezone.utc
@@ -289,7 +288,6 @@ If next_mirror_time is NULL, then mirroring of the branch is disabled.
     ...     name="mirror-disabled",
     ...     owner=no_priv,
     ...     url="http://example.com/disabled";,
-    ...     title="Disabled branch",
     ... )
     >>> branch.next_mirror_time = None
     >>> flush_database_updates()
diff --git a/lib/lp/code/stories/webservice/xx-branch.rst b/lib/lp/code/stories/webservice/xx-branch.rst
index 6d27ce6..3417ecc 100644
--- a/lib/lp/code/stories/webservice/xx-branch.rst
+++ b/lib/lp/code/stories/webservice/xx-branch.rst
@@ -84,7 +84,6 @@ time goes on.
     ...     owner=eric,
     ...     product=fooix,
     ...     name="trunk",
-    ...     title="The Fooix Trunk",
     ...     date_created=datetime(2009, 1, 1, tzinfo=timezone.utc),
     ... )
     >>> feature_branch = factory.makeAnyBranch(
diff --git a/lib/lp/code/vocabularies/branch.py b/lib/lp/code/vocabularies/branch.py
index 1e35f6c..620d9ba 100644
--- a/lib/lp/code/vocabularies/branch.py
+++ b/lib/lp/code/vocabularies/branch.py
@@ -24,16 +24,16 @@ from lp.services.webapp.interfaces import ILaunchBag
 from lp.services.webapp.vocabulary import (
     CountableIterator,
     IHugeVocabulary,
-    SQLObjectVocabularyBase,
+    StormVocabularyBase,
 )
 
 
 @implementer(IHugeVocabulary)
-class BranchVocabulary(SQLObjectVocabularyBase):
+class BranchVocabulary(StormVocabularyBase):
     """A vocabulary for searching branches."""
 
     _table = Branch
-    _orderBy = ["name", "id"]
+    _order_by = ["name", "id"]
     displayname = "Select a branch"
     step_title = "Search"
 
diff --git a/lib/lp/registry/model/distribution.py b/lib/lp/registry/model/distribution.py
index cf764f9..6427a0f 100644
--- a/lib/lp/registry/model/distribution.py
+++ b/lib/lp/registry/model/distribution.py
@@ -1085,7 +1085,7 @@ class Distribution(
             IStore(self)
             .using(
                 Branch,
-                Join(DistroSeries, DistroSeries.id == Branch.distroseriesID),
+                Join(DistroSeries, DistroSeries.id == Branch.distroseries_id),
                 LeftJoin(
                     Join(
                         SeriesSourcePackageBranch,
diff --git a/lib/lp/registry/model/productseries.py b/lib/lp/registry/model/productseries.py
index 0bd1479..dc347e1 100644
--- a/lib/lp/registry/model/productseries.py
+++ b/lib/lp/registry/model/productseries.py
@@ -14,7 +14,7 @@ from operator import itemgetter
 
 from lazr.delegates import delegate_to
 from storm.expr import Max, Sum
-from storm.locals import And, Desc
+from storm.locals import And, Desc, Int, Reference
 from storm.store import Store
 from zope.component import getUtility
 from zope.interface import implementer
@@ -130,7 +130,8 @@ class ProductSeries(
         notNull=False,
         default=None,
     )
-    branch = ForeignKey(foreignKey="Branch", dbName="branch", default=None)
+    branch_id = Int(name="branch", default=None)
+    branch = Reference(branch_id, "Branch.id")
 
     def validate_autoimport_mode(self, attr, value):
         # Perform the normal validation for None
@@ -152,12 +153,10 @@ class ProductSeries(
         default=TranslationsBranchImportMode.NO_IMPORT,
         validator=validate_autoimport_mode,
     )
-    translations_branch = ForeignKey(
-        dbName="translations_branch",
-        foreignKey="Branch",
-        notNull=False,
-        default=None,
+    translations_branch_id = Int(
+        name="translations_branch", allow_none=True, default=None
     )
+    translations_branch = Reference(translations_branch_id, "Branch.id")
     # where are the tarballs released from this branch placed?
     releasefileglob = StringCol(default=None)
     releaseverstyle = StringCol(default=None)
diff --git a/lib/lp/snappy/model/snap.py b/lib/lp/snappy/model/snap.py
index 5730bff..c55f435 100644
--- a/lib/lp/snappy/model/snap.py
+++ b/lib/lp/snappy/model/snap.py
@@ -1851,7 +1851,7 @@ class SnapSet:
         # Add branch/repository owners to the list of pre-loaded persons.
         # We need the target repository owner as well; unlike branches,
         # repository unique names aren't trigger-maintained.
-        person_ids.update(branch.ownerID for branch in branches)
+        person_ids.update(branch.owner_id for branch in branches)
         person_ids.update(repository.owner_id for repository in repositories)
 
         list(