← Back to team overview

launchpad-reviewers team mailing list archive

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

 

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

Commit message:
Convert Product to Storm

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/450113

There are a number of places that try to handle either a `Distribution` or a `Product` using similar code, so to avoid too much hassle I also converted `Distribution.owner` and `Distribution.driver` to the new style.  For similar reasons I ended up changing `SQLObjectVocabularyBase.__contains__` to accept `Storm` objects as well as `SQLBase` objects and to use Storm-style query syntax; in some ways this is a little odd, but `SQLObjectVocabularyBase` only has a few users left and will go away soon.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:stormify-product into launchpad:master.
diff --git a/lib/lp/bugs/doc/bugnotificationrecipients.rst b/lib/lp/bugs/doc/bugnotificationrecipients.rst
index c3688c9..ecbe624 100644
--- a/lib/lp/bugs/doc/bugnotificationrecipients.rst
+++ b/lib/lp/bugs/doc/bugnotificationrecipients.rst
@@ -71,8 +71,8 @@ Let's up some data for our test:
     ... )
     >>> debian = Distribution.selectOneBy(name="debian")
     >>> pmount = debian.getSourcePackage("pmount")
-    >>> alsa_utils = Product.selectOneBy(name="alsa-utils")
-    >>> gnomebaker = Product.selectOneBy(name="gnomebaker")
+    >>> alsa_utils = IStore(Product).find(Product, name="alsa-utils").one()
+    >>> gnomebaker = IStore(Product).find(Product, name="gnomebaker").one()
     >>> personset = getUtility(IPersonSet)
 
 Here's where getBugNotificationRecipients() starts off. First, a
diff --git a/lib/lp/codehosting/tests/test_acceptance.py b/lib/lp/codehosting/tests/test_acceptance.py
index ecf597a..ece2d62 100644
--- a/lib/lp/codehosting/tests/test_acceptance.py
+++ b/lib/lp/codehosting/tests/test_acceptance.py
@@ -35,6 +35,7 @@ from lp.codehosting.vfs import branch_id_to_path
 from lp.registry.model.person import Person
 from lp.registry.model.product import Product
 from lp.services.config import config
+from lp.services.database.interfaces import IStore
 from lp.services.testing.profiled import profiled
 from lp.testing import TestCaseWithFactory
 from lp.testing.layers import ZopelessAppServerLayer
@@ -210,7 +211,7 @@ class SSHTestCase(TestCaseWithTransport, LoomTestMixin, TestCaseWithFactory):
         if productName is None:
             product = None
         else:
-            product = Product.selectOneBy(name=productName)
+            product = IStore(Product).find(Product, name=productName).one()
         namespace = get_branch_namespace(owner, product)
         return namespace.getByName(branchName)
 
@@ -338,7 +339,7 @@ class AcceptanceTests(WithScenarios, SSHTestCase):
         if product_name == "+junk":
             product = None
         else:
-            product = Product.selectOneBy(name=product_name)
+            product = IStore(Product).find(Product, name=product_name).one()
         if branch_type == BranchType.MIRRORED:
             url = "http://example.com";
         else:
@@ -399,7 +400,10 @@ class AcceptanceTests(WithScenarios, SSHTestCase):
         ZopelessAppServerLayer.txn.begin()
         branch = self.getDatabaseBranch("testuser", None, "test-branch")
         branch.owner.name = "renamed-user"
-        branch.setTarget(user=branch.owner, project=Product.byName("firefox"))
+        branch.setTarget(
+            user=branch.owner,
+            project=IStore(Product).find(Product, name="firefox").one(),
+        )
         branch.name = "renamed-branch"
         ZopelessAppServerLayer.txn.commit()
 
@@ -542,7 +546,7 @@ class AcceptanceTests(WithScenarios, SSHTestCase):
         # We can also push branches to URLs like /+branch/firefox
         # Hack 'firefox' so we have permission to do this.
         ZopelessAppServerLayer.txn.begin()
-        firefox = Product.selectOneBy(name="firefox")
+        firefox = IStore(Product).find(Product, name="firefox").one()
         testuser = Person.selectOneBy(name="testuser")
         firefox.development_focus.owner = testuser
         ZopelessAppServerLayer.txn.commit()
diff --git a/lib/lp/registry/doc/commercialsubscription.rst b/lib/lp/registry/doc/commercialsubscription.rst
index 6912fe4..fe06b94 100644
--- a/lib/lp/registry/doc/commercialsubscription.rst
+++ b/lib/lp/registry/doc/commercialsubscription.rst
@@ -346,8 +346,9 @@ their commercial subscription was modified.
 All the products are returned when no parameters are passed in.
 
     >>> from lp.registry.model.product import Product
+    >>> from lp.services.database.interfaces import IStore
     >>> review_listing = product_set.forReview(commercial_member)
-    >>> review_listing.count() == Product.select().count()
+    >>> review_listing.count() == IStore(Product).find(Product).count()
     True
 
 The full text search will not match strings with dots in their name
diff --git a/lib/lp/registry/model/distribution.py b/lib/lp/registry/model/distribution.py
index 7045053..81040cb 100644
--- a/lib/lp/registry/model/distribution.py
+++ b/lib/lp/registry/model/distribution.py
@@ -261,12 +261,12 @@ class Distribution(
         dbName="mugshot", foreignKey="LibraryFileAlias", default=None
     )
     domainname = StringCol(notNull=True)
-    owner = ForeignKey(
-        dbName="owner",
-        foreignKey="Person",
-        storm_validator=validate_person_or_closed_team,
-        notNull=True,
+    owner_id = Int(
+        name="owner",
+        validator=validate_person_or_closed_team,
+        allow_none=False,
     )
+    owner = Reference(owner_id, "Person.id")
     registrant = ForeignKey(
         dbName="registrant",
         foreignKey="Person",
@@ -282,13 +282,13 @@ class Distribution(
     )
     bug_reporting_guidelines = StringCol(default=None)
     bug_reported_acknowledgement = StringCol(default=None)
-    driver = ForeignKey(
-        dbName="driver",
-        foreignKey="Person",
-        storm_validator=validate_public_person,
-        notNull=False,
+    driver_id = Int(
+        name="driver",
+        validator=validate_public_person,
+        allow_none=True,
         default=None,
     )
+    driver = Reference(driver_id, "Person.id")
     members = ForeignKey(
         dbName="members",
         foreignKey="Person",
diff --git a/lib/lp/registry/model/person.py b/lib/lp/registry/model/person.py
index 1aa22bc..6514417 100644
--- a/lib/lp/registry/model/person.py
+++ b/lib/lp/registry/model/person.py
@@ -203,7 +203,6 @@ from lp.services.database.sqlbase import (
     SQLBase,
     convert_storm_clause_to_string,
     cursor,
-    quote,
     sqlvalues,
 )
 from lp.services.database.sqlobject import (
@@ -1181,12 +1180,12 @@ class Person(
             ownership_participation = ClassAlias(TeamParticipation)
             clauses.extend(
                 [
-                    Product._ownerID == ownership_participation.team_id,
+                    Product._owner_id == ownership_participation.team_id,
                     ownership_participation.person_id == self.id,
                 ]
             )
         else:
-            clauses.append(Product._ownerID == self.id)
+            clauses.append(Product._owner_id == self.id)
 
         # We only want to use the extra query if match_name is not None and it
         # is not the empty string ('' or u'').
@@ -1269,11 +1268,11 @@ class Person(
                     TeamParticipation, Person.id == TeamParticipation.person_id
                 ),
                 LeftJoin(
-                    Product, TeamParticipation.team_id == Product._ownerID
+                    Product, TeamParticipation.team_id == Product._owner_id
                 ),
                 LeftJoin(
                     Distribution,
-                    TeamParticipation.team_id == Distribution.ownerID,
+                    TeamParticipation.team_id == Distribution.owner_id,
                 ),
                 Join(
                     CommercialSubscription,
@@ -1300,31 +1299,25 @@ class Person(
         The given pillar must be either an IProduct or an IDistribution.
         """
         if IProduct.providedBy(pillar):
-            where_clause = "product = %s" % sqlvalues(pillar)
+            pillar_clause = KarmaCache.product == pillar
         elif IDistribution.providedBy(pillar):
-            where_clause = "distribution = %s" % sqlvalues(pillar)
+            pillar_clause = KarmaCache.distribution == pillar
         else:
             raise AssertionError(
                 "Pillar must be a product or distro, got %s" % pillar
             )
-        replacements = sqlvalues(person=self)
-        replacements["where_clause"] = where_clause
-        query = (
-            """
-            SELECT DISTINCT KarmaCategory.id
-            FROM KarmaCategory
-            JOIN KarmaCache ON KarmaCache.category = KarmaCategory.id
-            WHERE %(where_clause)s
-                AND category IS NOT NULL
-                AND person = %(person)s
-            """
-            % replacements
-        )
-        cur = cursor()
-        cur.execute(query)
-        ids = [id for [id] in cur.fetchall()]
-        return IStore(KarmaCategory).find(
-            KarmaCategory, KarmaCategory.id.is_in(ids)
+        return (
+            IStore(KarmaCategory)
+            .using(
+                KarmaCategory,
+                Join(KarmaCache, KarmaCache.category == KarmaCategory.id),
+            )
+            .find(
+                KarmaCategory,
+                pillar_clause,
+                IsNot(KarmaCache.category_id, None),
+                KarmaCache.person == self,
+            )
         )
 
     @property
@@ -4561,14 +4554,14 @@ class PersonSet:
 
     def getPeopleWithBranches(self, product=None):
         """See `IPersonSet`."""
-        branch_clause = "SELECT owner FROM Branch"
+        # Circular import.
+        from lp.code.model.branch import Branch
+
+        branch_params = {}
         if product is not None:
-            branch_clause += " WHERE product = %s" % quote(product)
-        return Person.select(
-            """
-            Person.id in (%s)
-            """
-            % branch_clause
+            branch_params["where"] = Branch.product == product
+        return IStore(Person).find(
+            Person, Person.id.is_in(Select(Branch.owner_id, **branch_params))
         )
 
     def updatePersonalStandings(self):
diff --git a/lib/lp/registry/model/pillar.py b/lib/lp/registry/model/pillar.py
index 580d50d..de817fb 100644
--- a/lib/lp/registry/model/pillar.py
+++ b/lib/lp/registry/model/pillar.py
@@ -307,7 +307,7 @@ class PillarNameSet:
             pillar_names = set(rows).union(
                 load_related(PillarName, rows, ["alias_for"])
             )
-            pillars = load_related(Product, pillar_names, ["productID"])
+            pillars = load_related(Product, pillar_names, ["product_id"])
             pillars.extend(
                 load_related(ProjectGroup, pillar_names, ["projectgroup_id"])
             )
@@ -333,7 +333,8 @@ class PillarName(SQLBase):
     name = StringCol(
         dbName="name", notNull=True, unique=True, alternateID=True
     )
-    product = ForeignKey(foreignKey="Product", dbName="product")
+    product_id = Int(name="product", allow_none=True)
+    product = Reference(product_id, "Product.id")
     projectgroup_id = Int(name="project", allow_none=True)
     projectgroup = Reference(projectgroup_id, "ProjectGroup.id")
     distribution = ForeignKey(foreignKey="Distribution", dbName="distribution")
diff --git a/lib/lp/registry/model/product.py b/lib/lp/registry/model/product.py
index f828474..f3bb669 100644
--- a/lib/lp/registry/model/product.py
+++ b/lib/lp/registry/model/product.py
@@ -31,7 +31,16 @@ from storm.expr import (
     Or,
     Select,
 )
-from storm.locals import Int, List, Reference, ReferenceSet, Store, Unicode
+from storm.locals import (
+    Bool,
+    DateTime,
+    Int,
+    List,
+    Reference,
+    ReferenceSet,
+    Store,
+    Unicode,
+)
 from zope.component import getUtility
 from zope.event import notify
 from zope.interface import implementer
@@ -143,17 +152,10 @@ from lp.registry.model.sourcepackagename import SourcePackageName
 from lp.registry.model.teammembership import TeamParticipation
 from lp.services.database import bulk
 from lp.services.database.constants import 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 IStore
-from lp.services.database.sqlbase import SQLBase, sqlvalues
-from lp.services.database.sqlobject import (
-    BoolCol,
-    ForeignKey,
-    SQLObjectNotFound,
-    StringCol,
-)
+from lp.services.database.stormbase import StormBase
 from lp.services.database.stormexpr import (
     ArrayAgg,
     ArrayIntersects,
@@ -245,7 +247,7 @@ specification_policy_default = {
 
 @implementer(IBugSummaryDimension, IHasCustomLanguageCodes, IProduct)
 class Product(
-    SQLBase,
+    StormBase,
     BugTargetBase,
     HasDriversMixin,
     OfficialBugTagTargetMixin,
@@ -269,65 +271,60 @@ class Product(
 ):
     """A Product."""
 
-    _table = "Product"
+    __storm_table__ = "Product"
 
+    id = Int(primary=True)
     projectgroup_id = Int(name="project", allow_none=True, default=None)
     projectgroup = Reference(projectgroup_id, "ProjectGroup.id")
-    _owner = ForeignKey(
-        dbName="owner",
-        foreignKey="Person",
-        storm_validator=validate_person_or_closed_team,
-        notNull=True,
+    _owner_id = Int(
+        name="owner",
+        validator=validate_person_or_closed_team,
+        allow_none=False,
     )
-    registrant = ForeignKey(
-        dbName="registrant",
-        foreignKey="Person",
-        storm_validator=validate_public_person,
-        notNull=True,
+    _owner = Reference(_owner_id, "Person.id")
+    registrant_id = Int(
+        name="registrant", validator=validate_public_person, allow_none=False
     )
-    bug_supervisor = ForeignKey(
-        dbName="bug_supervisor",
-        foreignKey="Person",
-        storm_validator=validate_person,
-        notNull=False,
-        default=None,
-    )
-    driver = ForeignKey(
-        dbName="driver",
-        foreignKey="Person",
-        storm_validator=validate_person,
-        notNull=False,
+    registrant = Reference(registrant_id, "Person.id")
+    bug_supervisor_id = Int(
+        name="bug_supervisor",
+        validator=validate_person,
+        allow_none=True,
         default=None,
     )
-    name = StringCol(
-        dbName="name", notNull=True, alternateID=True, unique=True
+    bug_supervisor = Reference(bug_supervisor_id, "Person.id")
+    driver_id = Int(
+        name="driver", validator=validate_person, allow_none=True, default=None
     )
-    display_name = StringCol(dbName="displayname", notNull=True)
-    _title = StringCol(dbName="title", notNull=True)
-    summary = StringCol(dbName="summary", notNull=True)
-    description = StringCol(notNull=False, default=None)
-    datecreated = UtcDateTimeCol(
-        dbName="datecreated", notNull=True, default=UTC_NOW
+    driver = Reference(driver_id, "Person.id")
+    name = Unicode(name="name", allow_none=False)
+    display_name = Unicode(name="displayname", allow_none=False)
+    _title = Unicode(name="title", allow_none=False)
+    summary = Unicode(name="summary", allow_none=False)
+    description = Unicode(allow_none=True, default=None)
+    datecreated = DateTime(
+        name="datecreated",
+        allow_none=False,
+        default=UTC_NOW,
+        tzinfo=timezone.utc,
     )
-    homepageurl = StringCol(dbName="homepageurl", notNull=False, default=None)
-    homepage_content = StringCol(default=None)
-    icon_id = Int(name="icon", default=None)
+    homepageurl = Unicode(name="homepageurl", allow_none=True, default=None)
+    homepage_content = Unicode(default=None)
+    icon_id = Int(name="icon", allow_none=True, default=None)
     icon = Reference(icon_id, "LibraryFileAlias.id")
-    logo = ForeignKey(
-        dbName="logo", foreignKey="LibraryFileAlias", default=None
+    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")
+    screenshotsurl = Unicode(
+        name="screenshotsurl", allow_none=True, default=None
     )
-    mugshot = ForeignKey(
-        dbName="mugshot", foreignKey="LibraryFileAlias", default=None
+    wikiurl = Unicode(name="wikiurl", allow_none=True, default=None)
+    programminglang = Unicode(
+        name="programminglang", allow_none=True, default=None
     )
-    screenshotsurl = StringCol(
-        dbName="screenshotsurl", notNull=False, default=None
-    )
-    wikiurl = StringCol(dbName="wikiurl", notNull=False, default=None)
-    programminglang = StringCol(
-        dbName="programminglang", notNull=False, default=None
-    )
-    downloadurl = StringCol(dbName="downloadurl", notNull=False, default=None)
-    lastdoap = StringCol(dbName="lastdoap", notNull=False, default=None)
+    downloadurl = Unicode(name="downloadurl", allow_none=True, default=None)
+    lastdoap = Unicode(name="lastdoap", allow_none=True, default=None)
     translationgroup_id = Int(
         name="translationgroup", allow_none=True, default=None
     )
@@ -344,14 +341,14 @@ class Product(
     translation_focus = Reference(translation_focus_id, "ProductSeries.id")
     bugtracker_id = Int(name="bugtracker", allow_none=True, default=None)
     bugtracker = Reference(bugtracker_id, "BugTracker.id")
-    official_answers = BoolCol(
-        dbName="official_answers", notNull=True, default=False
+    official_answers = Bool(
+        name="official_answers", allow_none=False, default=False
     )
-    official_blueprints = BoolCol(
-        dbName="official_blueprints", notNull=True, default=False
+    official_blueprints = Bool(
+        name="official_blueprints", allow_none=False, default=False
     )
-    official_malone = BoolCol(
-        dbName="official_malone", notNull=True, default=False
+    official_malone = Bool(
+        name="official_malone", allow_none=False, default=False
     )
     remote_product = Unicode(
         name="remote_product", allow_none=True, default=None
@@ -363,6 +360,70 @@ class Product(
     # to an artifact in the policy is sufficient for access.
     access_policies = List(type=Int())
 
+    _creating = False
+
+    def __init__(
+        self,
+        owner,
+        registrant,
+        name,
+        display_name,
+        title,
+        summary,
+        projectgroup=None,
+        bug_supervisor=None,
+        driver=None,
+        description=None,
+        homepageurl=None,
+        icon=None,
+        logo=None,
+        mugshot=None,
+        screenshotsurl=None,
+        wikiurl=None,
+        programminglang=None,
+        downloadurl=None,
+        vcs=None,
+        information_type=InformationType.PUBLIC,
+        project_reviewed=False,
+        sourceforgeproject=None,
+        license_info=None,
+    ):
+        super().__init__()
+        try:
+            self._creating = True
+            self.owner = owner
+            self.registrant = registrant
+            self.name = name
+            self.display_name = display_name
+            self._title = title
+            self.summary = summary
+            self.projectgroup = projectgroup
+            self.bug_supervisor = bug_supervisor
+            self.driver = driver
+            self.description = description
+            self.homepageurl = homepageurl
+            self.icon = icon
+            self.logo = logo
+            self.mugshot = mugshot
+            self.screenshotsurl = screenshotsurl
+            self.wikiurl = wikiurl
+            self.programminglang = programminglang
+            self.downloadurl = downloadurl
+            self.vcs = vcs
+            self.information_type = information_type
+            self.project_reviewed = project_reviewed
+            self.sourceforgeproject = sourceforgeproject
+            self.license_info = license_info
+        except Exception:
+            # If validating references such as `owner` fails, then the new
+            # object may have been added to the store first.  Remove it
+            # again in that case.
+            store = Store.of(self)
+            if store is not None:
+                store.remove(self)
+            raise
+        del self._creating
+
     @property
     def displayname(self):
         return self.display_name
@@ -417,7 +478,7 @@ class Product(
         if value in PROPRIETARY_INFORMATION_TYPES:
             if self.answers_usage == ServiceUsage.LAUNCHPAD:
                 yield CannotChangeInformationType("Answers is enabled.")
-        if self._SO_creating or value not in PROPRIETARY_INFORMATION_TYPES:
+        if self._creating or value not in PROPRIETARY_INFORMATION_TYPES:
             return
         # Additional checks when transitioning an existing product to a
         # proprietary type
@@ -524,7 +585,7 @@ class Product(
         # maintainer as required for the Product.
         # However, only on edits. If this is a new Product it's handled
         # already.
-        if not self._SO_creating:
+        if not self._creating:
             if (
                 old_info_type == InformationType.PUBLIC
                 and value != InformationType.PUBLIC
@@ -644,11 +705,11 @@ class Product(
         """See `HasMilestonesMixin`."""
         return Milestone.product == self
 
-    enable_bug_expiration = BoolCol(
-        dbName="enable_bug_expiration", notNull=True, default=False
+    enable_bug_expiration = Bool(
+        name="enable_bug_expiration", allow_none=False, default=False
     )
-    project_reviewed = BoolCol(dbName="reviewed", notNull=True, default=False)
-    reviewer_whiteboard = StringCol(notNull=False, default=None)
+    project_reviewed = Bool(name="reviewed", allow_none=False, default=False)
+    reviewer_whiteboard = Unicode(allow_none=True, default=None)
     private_bugs = False
     bug_sharing_policy = DBEnum(
         enum=BugSharingPolicy, allow_none=True, default=None
@@ -661,9 +722,9 @@ class Product(
         allow_none=True,
         default=SpecificationSharingPolicy.PUBLIC,
     )
-    autoupdate = BoolCol(dbName="autoupdate", notNull=True, default=False)
+    autoupdate = Bool(name="autoupdate", allow_none=False, default=False)
     freshmeatproject = None
-    sourceforgeproject = StringCol(notNull=False, default=None)
+    sourceforgeproject = Unicode(allow_none=True, default=None)
     # While the interface defines this field as required, we need to
     # allow it to be NULL so we can create new product records before
     # the corresponding series records.
@@ -671,9 +732,9 @@ class Product(
         name="development_focus", allow_none=True, default=None
     )
     development_focus = Reference(development_focus_id, "ProductSeries.id")
-    bug_reporting_guidelines = StringCol(default=None)
-    bug_reported_acknowledgement = StringCol(default=None)
-    enable_bugfiling_duplicate_search = BoolCol(notNull=True, default=True)
+    bug_reporting_guidelines = Unicode(default=None)
+    bug_reported_acknowledgement = Unicode(default=None)
+    enable_bugfiling_duplicate_search = Bool(allow_none=False, default=True)
 
     def _validate_active(self, attr, value):
         # Validate deactivation.
@@ -685,29 +746,27 @@ class Product(
                 )
         return value
 
-    active = BoolCol(
-        dbName="active",
-        notNull=True,
+    active = Bool(
+        name="active",
+        allow_none=False,
         default=True,
-        storm_validator=_validate_active,
+        validator=_validate_active,
     )
 
     def _validate_license_info(self, attr, value):
-        if not self._SO_creating and value != self.license_info:
+        if not self._creating and value != self.license_info:
             # Clear the project_reviewed and license_approved flags
             # if the licence changes.
             self._resetLicenseReview()
         return value
 
-    license_info = StringCol(
-        dbName="license_info",
-        default=None,
-        storm_validator=_validate_license_info,
+    license_info = Unicode(
+        name="license_info", default=None, validator=_validate_license_info
     )
 
     def _validate_license_approved(self, attr, value):
         """Ensure licence approved is only applied to the correct licences."""
-        if not self._SO_creating:
+        if not self._creating:
             licenses = list(self.licenses)
             if value:
                 if (
@@ -723,11 +782,11 @@ class Product(
                 self.project_reviewed = True
         return value
 
-    license_approved = BoolCol(
-        dbName="license_approved",
-        notNull=True,
+    license_approved = Bool(
+        name="license_approved",
+        allow_none=False,
         default=False,
-        storm_validator=_validate_license_approved,
+        validator=_validate_license_approved,
     )
 
     def getAllowedBugInformationTypes(self):
@@ -1720,7 +1779,7 @@ def get_precached_products(
         for attr_name in role_names:
             person_ids.update(
                 map(
-                    lambda x: getattr(x, attr_name + "ID"),
+                    lambda x: getattr(x, attr_name + "_id"),
                     products_by_id.values(),
                 )
             )
@@ -1880,7 +1939,7 @@ class ProductSet:
             return result
 
         def do_eager_load(rows):
-            owner_ids = set(map(operator.attrgetter("_ownerID"), rows))
+            owner_ids = set(map(operator.attrgetter("_owner_id"), rows))
             # +detailed-listing renders the person with team branding.
             list(
                 getUtility(IPersonSet).getPrecachedPersonsFromIDs(
@@ -1892,12 +1951,12 @@ class ProductSet:
 
     def get(self, productid):
         """See `IProductSet`."""
-        try:
-            return Product.get(productid)
-        except SQLObjectNotFound:
+        product = IStore(Product).get(Product, productid)
+        if product is None:
             raise NotFoundError(
                 "Product with ID %s does not exist" % str(productid)
             )
+        return product
 
     def getByName(self, name, ignore_inactive=False):
         """See `IProductSet`."""
@@ -1908,18 +1967,25 @@ class ProductSet:
 
     def getProductsWithBranches(self, num_products=None):
         """See `IProductSet`."""
-        results = Product.select(
-            """
-            Product.id in (
-                select distinct(product) from Branch
-                where lifecycle_status in %s)
-            and Product.active
-            """
-            % sqlvalues(DEFAULT_BRANCH_STATUS_IN_LISTING),
-            orderBy="name",
+        results = (
+            IStore(Product)
+            .find(
+                Product,
+                Product.id.is_in(
+                    Select(
+                        Branch.product_id,
+                        where=Branch.lifecycle_status.is_in(
+                            DEFAULT_BRANCH_STATUS_IN_LISTING
+                        ),
+                        distinct=True,
+                    )
+                ),
+                Product.active,
+            )
+            .order_by(Product.name)
         )
         if num_products is not None:
-            results = results.limit(num_products)
+            results = results[:num_products]
         return results
 
     def createProduct(
@@ -1975,7 +2041,7 @@ class ProductSet:
             registrant=registrant,
             name=name,
             display_name=display_name,
-            _title=title,
+            title=title,
             projectgroup=projectgroup,
             summary=summary,
             description=description,
@@ -1983,7 +2049,6 @@ class ProductSet:
             screenshotsurl=screenshotsurl,
             wikiurl=wikiurl,
             downloadurl=downloadurl,
-            freshmeatproject=None,
             sourceforgeproject=sourceforgeproject,
             programminglang=programminglang,
             project_reviewed=project_reviewed,
@@ -2211,7 +2276,7 @@ class ProductSet:
                 Product.id == ProductSeries.product_id,
                 POTemplate.productseries_id == ProductSeries.id,
                 Product.translations_usage == ServiceUsage.LAUNCHPAD,
-                Person.id == Product._ownerID,
+                Person.id == Product._owner_id,
             )
             .config(distinct=True)
             .order_by(Product.display_name)
diff --git a/lib/lp/registry/model/projectgroup.py b/lib/lp/registry/model/projectgroup.py
index 81457e3..668be1d 100644
--- a/lib/lp/registry/model/projectgroup.py
+++ b/lib/lp/registry/model/projectgroup.py
@@ -175,6 +175,7 @@ class ProjectGroup(
         logo=None,
         mugshot=None,
     ):
+        super().__init__()
         try:
             self.owner = owner
             self.registrant = registrant
@@ -221,7 +222,9 @@ class ProjectGroup(
         return list(self.getProducts(getUtility(ILaunchBag).user))
 
     def getProduct(self, name):
-        return Product.selectOneBy(projectgroup=self, name=name)
+        return (
+            IStore(Product).find(Product, projectgroup=self, name=name).one()
+        )
 
     def getConfigurableProducts(self):
         return [
@@ -336,7 +339,7 @@ class ProjectGroup(
         """See `OfficialBugTagTargetMixin`."""
         And(
             ProjectGroup.id == Product.projectgroup_id,
-            Product.id == OfficialBugTag.productID,
+            Product.id == OfficialBugTag.product_id,
         )
 
     @property
diff --git a/lib/lp/registry/services/sharingservice.py b/lib/lp/registry/services/sharingservice.py
index 5fc8c27..65f355b 100644
--- a/lib/lp/registry/services/sharingservice.py
+++ b/lib/lp/registry/services/sharingservice.py
@@ -162,13 +162,13 @@ class SharingService:
             teams_sql = SQL("SELECT team from teams")
             store = store.with_(with_statement)
             if IProduct.implementedBy(pillar_class):
-                ownerID = pillar_class._ownerID
+                owner_id = pillar_class._owner_id
             else:
-                ownerID = pillar_class.ownerID
+                owner_id = pillar_class.owner_id
             filter = Or(
                 extra_filter or False,
-                ownerID.is_in(teams_sql),
-                pillar_class.driverID.is_in(teams_sql),
+                owner_id.is_in(teams_sql),
+                pillar_class.driver_id.is_in(teams_sql),
             )
         tables = [
             AccessPolicyGrantFlat,
diff --git a/lib/lp/registry/stories/product/xx-product-index.rst b/lib/lp/registry/stories/product/xx-product-index.rst
index 06afe68..ec30192 100644
--- a/lib/lp/registry/stories/product/xx-product-index.rst
+++ b/lib/lp/registry/stories/product/xx-product-index.rst
@@ -27,13 +27,14 @@ Evolution has no external links.
 Now update Tomcat to actually have this data:
 
     >>> import transaction
-    >>> from lp.services.database.sqlbase import flush_database_updates
     >>> from lp.registry.enums import VCSType
     >>> from lp.registry.model.product import Product
+    >>> from lp.services.database.interfaces import IStore
+    >>> from lp.services.database.sqlbase import flush_database_updates
     >>> from lp.testing import ANONYMOUS, login, logout
     >>> login(ANONYMOUS)
 
-    >>> tomcat = Product.selectOneBy(name="tomcat")
+    >>> tomcat = IStore(Product).find(Product, name="tomcat").one()
     >>> tomcat.vcs = VCSType.GIT
     >>> tomcat.homepageurl = "http://home.page/";
     >>> tomcat.sourceforgeproject = "sf-tomcat"
@@ -67,7 +68,7 @@ When the sourceforge URL is identical to the homepage, we omit the homepage:
 
     >>> login(ANONYMOUS)
 
-    >>> tomcat = Product.selectOneBy(name="tomcat")
+    >>> tomcat = IStore(Product).find(Product, name="tomcat").one()
     >>> tomcat.homepageurl = "http://sourceforge.net/projects/sf-tomcat";
 
     >>> logout()
@@ -130,7 +131,7 @@ If the project doesn't qualify for free hosting, or if it doesn't have
 much time left on its commercial subscription, a portlet is displayed to
 direct the owner to purchase a subscription.
 
-    >>> firefox = Product.selectOneBy(name="firefox")
+    >>> firefox = IStore(Product).find(Product, name="firefox").one()
     >>> ignored = login_person(firefox.owner)
     >>> firefox.licenses = [License.OTHER_PROPRIETARY]
     >>> firefox.license_info = "Internal project."
@@ -285,7 +286,9 @@ Aliases
 When a project has one or more aliases, they're shown on the project's
 home page.
 
-    >>> Product.byName("firefox").setAliases(["iceweasel", "snowchicken"])
+    >>> IStore(Product).find(Product, name="firefox").one().setAliases(
+    ...     ["iceweasel", "snowchicken"]
+    ... )
     >>> anon_browser.open("http://launchpad.test/firefox";)
     >>> print(extract_text(find_tag_by_id(anon_browser.contents, "aliases")))
     Also known as: iceweasel, snowchicken
diff --git a/lib/lp/registry/stories/productrelease/xx-productrelease-basics.rst b/lib/lp/registry/stories/productrelease/xx-productrelease-basics.rst
index da4df2c..f25171d 100644
--- a/lib/lp/registry/stories/productrelease/xx-productrelease-basics.rst
+++ b/lib/lp/registry/stories/productrelease/xx-productrelease-basics.rst
@@ -133,7 +133,8 @@ release too.
 
     >>> from lp.registry.model.person import Person
     >>> from lp.registry.model.product import Product
-    >>> tomcat = Product.selectOneBy(name="tomcat")
+    >>> from lp.services.database.interfaces import IStore
+    >>> tomcat = IStore(Product).find(Product, name="tomcat").one()
     >>> print(tomcat.owner.name)
     ubuntu-team
 
diff --git a/lib/lp/registry/stories/project/xx-project-index.rst b/lib/lp/registry/stories/project/xx-project-index.rst
index 6f170cb..dc3d406 100644
--- a/lib/lp/registry/stories/project/xx-project-index.rst
+++ b/lib/lp/registry/stories/project/xx-project-index.rst
@@ -158,14 +158,15 @@ Inactive products are not included in that list, though.
     # Use the DB classes directly to avoid having to setup a zope interaction
     # (i.e. login()) and bypass the security proxy.
     >>> from lp.registry.model.product import Product
-    >>> firefox = Product.byName("firefox")
+    >>> from lp.services.database.interfaces import IStore
+    >>> firefox = IStore(Product).find(Product, name="firefox").one()
 
     # Unlink the source packages so the project can be deactivated.
     >>> from lp.testing import unlink_source_packages
     >>> login("admin@xxxxxxxxxxxxx")
     >>> unlink_source_packages(firefox)
     >>> firefox.active = False
-    >>> firefox.syncUpdate()
+    >>> IStore(firefox).flush()
 
     >>> logout()
     >>> browser.open("http://launchpad.test/mozilla";)
@@ -176,7 +177,7 @@ Inactive products are not included in that list, though.
     <a...Mozilla Thunderbird</a>
 
     >>> firefox.active = True
-    >>> firefox.syncUpdate()
+    >>> IStore(firefox).flush()
 
 
 Project Group bug subscriptions
diff --git a/lib/lp/registry/tests/test_commercialprojects_vocabularies.py b/lib/lp/registry/tests/test_commercialprojects_vocabularies.py
index d9dbc16..3af8c17 100644
--- a/lib/lp/registry/tests/test_commercialprojects_vocabularies.py
+++ b/lib/lp/registry/tests/test_commercialprojects_vocabularies.py
@@ -54,7 +54,7 @@ class TestCommProjVocabulary(TestCaseWithFactory):
     def test_attributes(self):
         self.assertEqual("Select a commercial project", self.vocab.displayname)
         self.assertEqual("Search", self.vocab.step_title)
-        self.assertEqual("displayname", self.vocab._orderBy)
+        self.assertEqual("displayname", self.vocab._order_by)
 
     def test_searchForTerms_empty(self):
         # An empty search will return all active maintained projects.
diff --git a/lib/lp/registry/tests/test_product.py b/lib/lp/registry/tests/test_product.py
index 895ee15..607d872 100644
--- a/lib/lp/registry/tests/test_product.py
+++ b/lib/lp/registry/tests/test_product.py
@@ -1805,7 +1805,7 @@ class ProductAttributeCacheTestCase(TestCaseWithFactory):
 
     def setUp(self):
         super().setUp()
-        self.product = Product.selectOneBy(name="tomcat")
+        self.product = IStore(Product).find(Product, name="tomcat").one()
 
     def testLicensesCache(self):
         """License cache should be cleared automatically."""
diff --git a/lib/lp/registry/vocabularies.py b/lib/lp/registry/vocabularies.py
index 8642d22..c03b5c4 100644
--- a/lib/lp/registry/vocabularies.py
+++ b/lib/lp/registry/vocabularies.py
@@ -87,7 +87,6 @@ from zope.interface import implementer
 from zope.schema.interfaces import IVocabularyTokenized
 from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
 from zope.security.interfaces import Unauthorized
-from zope.security.proxy import isinstance as zisinstance
 from zope.security.proxy import removeSecurityProxy
 
 from lp.answers.interfaces.question import IQuestion
@@ -147,7 +146,7 @@ from lp.registry.model.teammembership import TeamParticipation
 from lp.services.database import bulk
 from lp.services.database.decoratedresultset import DecoratedResultSet
 from lp.services.database.interfaces import IStore
-from lp.services.database.sqlbase import SQLBase, sqlvalues
+from lp.services.database.sqlbase import sqlvalues
 from lp.services.database.sqlobject import AND, CONTAINSSTRING, OR
 from lp.services.database.stormexpr import (
     RegexpMatch,
@@ -247,27 +246,16 @@ class KarmaCategoryVocabulary(NamedStormVocabulary):
 
 
 @implementer(IHugeVocabulary)
-class ProductVocabulary(SQLObjectVocabularyBase):
+class ProductVocabulary(StormVocabularyBase):
     """All `IProduct` objects vocabulary."""
 
     step_title = "Search"
 
     _table = Product
-    _orderBy = "displayname"
+    _order_by = "displayname"
+    _clauses = [Product.active]
     displayname = "Select a project"
 
-    def __contains__(self, obj):
-        # Sometimes this method is called with an SQLBase instance, but
-        # z3 form machinery sends through integer ids. This might be due
-        # to a bug somewhere.
-        where = "active='t' AND id=%d"
-        if zisinstance(obj, SQLBase):
-            product = self._table.selectOne(where % obj.id)
-            return product is not None and product == obj
-        else:
-            product = self._table.selectOne(where % int(obj))
-            return product is not None
-
     def toTerm(self, obj):
         """See `IVocabulary`."""
         return SimpleTerm(obj, obj.name, obj.title)
@@ -276,7 +264,11 @@ class ProductVocabulary(SQLObjectVocabularyBase):
         """See `IVocabularyTokenized`."""
         # Product names are always lowercase.
         token = token.lower()
-        product = self._table.selectOneBy(name=token, active=True)
+        product = (
+            IStore(self._table)
+            .find(self._table, self._table.active, name=token)
+            .one()
+        )
         if product is None:
             raise LookupError(token)
         return self.toTerm(product)
@@ -584,7 +576,7 @@ class ValidPersonOrTeamVocabulary(
     @cachedproperty
     def store(self):
         """The storm store."""
-        return IStore(Product)
+        return IStore(Person)
 
     @cachedproperty
     def _karma_context_constraint(self):
@@ -1521,7 +1513,7 @@ class MilestoneWithDateExpectedVocabulary(MilestoneVocabulary):
 
 
 @implementer(IHugeVocabulary)
-class CommercialProjectsVocabulary(NamedSQLObjectVocabulary):
+class CommercialProjectsVocabulary(NamedStormVocabulary):
     """List all commercial projects.
 
     A commercial project is an active project that can have a commercial
@@ -1531,7 +1523,7 @@ class CommercialProjectsVocabulary(NamedSQLObjectVocabulary):
     """
 
     _table = Product
-    _orderBy = "displayname"
+    _order_by = "displayname"
     step_title = "Search"
 
     @property
@@ -1982,7 +1974,7 @@ class PillarVocabularyBase(NamedStormHugeVocabulary):
         store = IStore(PillarName)
         origin = [
             PillarName,
-            LeftJoin(Product, Product.id == PillarName.productID),
+            LeftJoin(Product, Product.id == PillarName.product_id),
         ]
         base_clauses = [
             ProductSet.getProductPrivacyFilter(getUtility(ILaunchBag).user)
diff --git a/lib/lp/scripts/harness.py b/lib/lp/scripts/harness.py
index 26ca7fe..d6dc0d0 100644
--- a/lib/lp/scripts/harness.py
+++ b/lib/lp/scripts/harness.py
@@ -75,7 +75,7 @@ def _get_locals():
         d = Distribution.get(1)
         p = Person.get(1)
         ds = DistroSeries.get(1)
-        prod = Product.get(1)
+        prod = store.get(Product, 1)
         proj = store.get(ProjectGroup, 1)
         b2 = store.get(Bug, 2)
         b1 = store.get(Bug, 1)
diff --git a/lib/lp/services/database/bulk.py b/lib/lp/services/database/bulk.py
index bec5499..a90dc0f 100644
--- a/lib/lp/services/database/bulk.py
+++ b/lib/lp/services/database/bulk.py
@@ -185,7 +185,7 @@ def load_related(object_type, owning_objects, foreign_keys):
     :param object_type: The object type to load - e.g. Person.
     :param owning_objects: The objects holding the references. E.g. Bug.
     :param foreign_keys: A list of attributes that should be inspected for
-        keys. e.g. ['ownerID']
+        keys. e.g. ['owner_id']
     """
     keys = set()
     for owning_object in owning_objects:
diff --git a/lib/lp/services/statistics/model/statistics.py b/lib/lp/services/statistics/model/statistics.py
index 11b5376..205b05c 100644
--- a/lib/lp/services/statistics/model/statistics.py
+++ b/lib/lp/services/statistics/model/statistics.py
@@ -18,12 +18,15 @@ from lp.answers.enums import QuestionStatus
 from lp.answers.model.question import Question
 from lp.app.enums import ServiceUsage
 from lp.blueprints.interfaces.specification import ISpecificationSet
+from lp.blueprints.model.specification import Specification
 from lp.bugs.model.bug import Bug
 from lp.bugs.model.bugtask import BugTask
 from lp.code.interfaces.branchcollection import IAllBranches
 from lp.code.interfaces.gitcollection import IAllGitRepositories
+from lp.code.model.branch import Branch
 from lp.registry.interfaces.person import IPersonSet
 from lp.registry.model.product import Product
+from lp.registry.model.productseries import ProductSeries
 from lp.services.database.constants import UTC_NOW
 from lp.services.database.interfaces import IStore
 from lp.services.database.sqlbase import cursor
@@ -115,7 +118,7 @@ class LaunchpadStatisticSet:
 
         self.update(
             "products_using_malone",
-            Product.selectBy(official_malone=True).count(),
+            store.find(Product, official_malone=True).count(),
         )
         ztm.commit()
 
@@ -139,65 +142,59 @@ class LaunchpadStatisticSet:
         ztm.commit()
 
     def _updateRegistryStatistics(self, ztm):
+        store = IStore(Product)
         self.update(
             "active_products",
-            Product.select("active IS TRUE", distinct=True).count(),
+            store.find(Product, Product.active).config(distinct=True).count(),
         )
         self.update(
             "products_with_translations",
-            Product.select(
-                """
-                POTemplate.productseries = ProductSeries.id AND
-                Product.id = ProductSeries.product AND
-                Product.active = TRUE
-                """,
-                clauseTables=["ProductSeries", "POTemplate"],
-                distinct=True,
-            ).count(),
+            store.find(
+                Product,
+                POTemplate.productseries == ProductSeries.id,
+                ProductSeries.product == Product.id,
+                Product.active,
+            )
+            .config(distinct=True)
+            .count(),
         )
         self.update(
             "products_with_blueprints",
-            Product.select(
-                "Specification.product=Product.id AND Product.active IS TRUE",
-                distinct=True,
-                clauseTables=["Specification"],
-            ).count(),
+            store.find(
+                Product, Specification.product == Product.id, Product.active
+            )
+            .config(distinct=True)
+            .count(),
         )
         self.update(
             "products_with_branches",
-            Product.select(
-                "Branch.product=Product.id AND Product.active IS TRUE",
-                distinct=True,
-                clauseTables=["Branch"],
-            ).count(),
+            store.find(Product, Branch.product == Product.id, Product.active)
+            .config(distinct=True)
+            .count(),
         )
         self.update(
             "products_with_bugs",
-            Product.select(
-                "BugTask.product=Product.id AND Product.active IS TRUE",
-                distinct=True,
-                clauseTables=["BugTask"],
-            ).count(),
+            store.find(Product, BugTask.product == Product.id, Product.active)
+            .config(distinct=True)
+            .count(),
         )
         self.update(
             "products_with_questions",
-            Product.select(
-                "Question.product=Product.id AND Product.active IS TRUE",
-                distinct=True,
-                clauseTables=["Question"],
-            ).count(),
+            store.find(Product, Question.product == Product.id, Product.active)
+            .config(distinct=True)
+            .count(),
         )
         self.update(
             "reviewed_products",
-            Product.selectBy(project_reviewed=True, active=True).count(),
+            store.find(Product, project_reviewed=True, active=True).count(),
         )
 
     def _updateRosettaStatistics(self, ztm):
         self.update(
             "products_using_rosetta",
-            Product.selectBy(
-                translations_usage=ServiceUsage.LAUNCHPAD
-            ).count(),
+            IStore(Product)
+            .find(Product, translations_usage=ServiceUsage.LAUNCHPAD)
+            .count(),
         )
         self.update(
             "potemplate_count", IStore(POTemplate).find(POTemplate).count()
diff --git a/lib/lp/services/webapp/vocabulary.py b/lib/lp/services/webapp/vocabulary.py
index 057ecbf..294dd8c 100644
--- a/lib/lp/services/webapp/vocabulary.py
+++ b/lib/lp/services/webapp/vocabulary.py
@@ -329,19 +329,19 @@ class SQLObjectVocabularyBase(FilteredVocabularyBase):
         # Sometimes this method is called with an SQLBase instance, but
         # z3 form machinery sends through integer ids. This might be due
         # to a bug somewhere.
-        if zisinstance(obj, SQLBase):
-            clause = self._table.q.id == obj.id
+        if zisinstance(obj, (SQLBase, Storm)):  # noqa: B1
+            clause = self._table.id == obj.id
             if self._filter:
                 # XXX kiko 2007-01-16: this code is untested.
                 clause = AND(clause, self._filter)
-            found_obj = self._table.selectOne(clause)
+            found_obj = IStore(self._table).find(self._table, clause).one()
             return found_obj is not None and found_obj == obj
         else:
-            clause = self._table.q.id == int(obj)
+            clause = self._table.id == int(obj)
             if self._filter:
                 # XXX kiko 2007-01-16: this code is untested.
                 clause = AND(clause, self._filter)
-            found_obj = self._table.selectOne(clause)
+            found_obj = IStore(self._table).find(self._table, clause).one()
             return found_obj is not None
 
     def getTerm(self, value):
diff --git a/lib/lp/translations/model/translationsoverview.py b/lib/lp/translations/model/translationsoverview.py
index 76c1a3f..793b1e5 100644
--- a/lib/lp/translations/model/translationsoverview.py
+++ b/lib/lp/translations/model/translationsoverview.py
@@ -8,6 +8,7 @@ from zope.interface import implementer
 from lp.app.enums import InformationType, ServiceUsage
 from lp.registry.model.distribution import Distribution
 from lp.registry.model.product import Product
+from lp.services.database.interfaces import IStore
 from lp.services.database.sqlbase import cursor, sqlvalues
 from lp.services.utils import round_half_up
 from lp.translations.interfaces.translationsoverview import (
@@ -94,7 +95,7 @@ class TranslationsOverview:
             if maximum is None or relative_karma > maximum:
                 maximum = relative_karma
             if product_id is not None:
-                pillar = Product.get(product_id)
+                pillar = IStore(Product).get(Product, product_id)
             elif distro_id is not None:
                 pillar = Distribution.get(distro_id)
             else:
diff --git a/lib/lp/translations/stories/productseries/xx-productseries-translations.rst b/lib/lp/translations/stories/productseries/xx-productseries-translations.rst
index 55d0792..d39fa47 100644
--- a/lib/lp/translations/stories/productseries/xx-productseries-translations.rst
+++ b/lib/lp/translations/stories/productseries/xx-productseries-translations.rst
@@ -180,7 +180,8 @@ Launchpad Translations.
 
     # Use the raw DB object to bypass the security proxy.
     >>> from lp.registry.model.product import Product
-    >>> product = Product.byName("bazaar")
+    >>> from lp.services.database.interfaces import IStore
+    >>> product = IStore(Product).find(Product, name="bazaar").one()
     >>> product.translations_usage = ServiceUsage.NOT_APPLICABLE
 
 When the owner now visits the upload page for trunk, there's a notice.
@@ -416,9 +417,8 @@ project admin does not see the link for configuring other branches.
 A new series is added.
 
     >>> from lp.registry.interfaces.series import SeriesStatus
-    >>> from lp.registry.model.product import Product
     >>> login("foo.bar@xxxxxxxxxxxxx")
-    >>> evolution = Product.byName("evolution")
+    >>> evolution = IStore(Product).find(Product, name="evolution").one()
     >>> series = factory.makeProductSeries(product=evolution, name="evo-new")
     >>> series.status = SeriesStatus.EXPERIMENTAL
     >>> logout()
diff --git a/lib/lp/translations/stories/standalone/xx-product-export.rst b/lib/lp/translations/stories/standalone/xx-product-export.rst
index 73edc9c..d0f7bce 100644
--- a/lib/lp/translations/stories/standalone/xx-product-export.rst
+++ b/lib/lp/translations/stories/standalone/xx-product-export.rst
@@ -47,9 +47,10 @@ Use the DB classes directly to avoid having to setup a zope interaction
 
     >>> from lp.app.enums import ServiceUsage
     >>> from lp.registry.model.product import Product
-    >>> product = Product.byName("evolution")
+    >>> from lp.services.database.interfaces import IStore
+    >>> product = IStore(Product).find(Product, name="evolution").one()
     >>> product.translations_usage = ServiceUsage.NOT_APPLICABLE
-    >>> product.sync()
+    >>> IStore(product).flush()
     >>> browser.open("http://translations.launchpad.test/evolution";)
     >>> browser.getLink("download")
     Traceback (most recent call last):
@@ -59,7 +60,7 @@ Use the DB classes directly to avoid having to setup a zope interaction
 Restore previous state for subsequent tests, and verify.
 
     >>> product.translations_usage = ServiceUsage.LAUNCHPAD
-    >>> product.sync()
+    >>> IStore(product).flush()
     >>> browser.open("http://translations.launchpad.test/evolution";)
     >>> browser.getLink("download") is not None
     True
diff --git a/lib/lp/translations/tests/test_autoapproval.py b/lib/lp/translations/tests/test_autoapproval.py
index de59805..eeadbb0 100644
--- a/lib/lp/translations/tests/test_autoapproval.py
+++ b/lib/lp/translations/tests/test_autoapproval.py
@@ -1203,7 +1203,7 @@ class TestCleanup(TestCaseWithFactory, GardenerDbUserMixin):
         self.assertTrue(self._exists(entry_id))
 
         entry.productseries.product.active = False
-        entry.productseries.product.syncUpdate()
+        self.store.flush()
 
         self.becomeTheGardener()
         self.queue._cleanUpInactiveProductEntries(self.store)